Rust for Java Programmers
Rust 面向 Java 程序员
AI-driven guide: Written with GPT-5.4 assistance for experienced Java developers who want to learn Rust with clear conceptual mapping and practical migration advice.
AI 驱动说明:本书由 GPT-5.4 协助撰写,面向已经有 Java 经验、希望系统学习 Rust 的开发者。
This book is a bridge text. It assumes comfort with Java, Maven or Gradle, the JVM, exceptions, interfaces, streams, and the usual enterprise toolkit. The goal is not to re-teach programming. The goal is to show which instincts transfer cleanly, which ones must change, and how to reach idiomatic Rust without dragging Java habits everywhere.
这本书定位成桥接教材。默认已经熟悉 Java、Maven 或 Gradle、JVM、异常、接口、Stream,以及常见企业开发工具。重点不是重复教授编程基础,而是说明哪些经验可以平移,哪些习惯必须调整,以及怎样走到更符合 Rust 社区习惯的写法。
Who This Book Is For
适合哪些读者
- Developers who already write Java for backend services, tooling, data pipelines, or libraries
已经用 Java 写后端服务、工具、数据处理程序或基础库的开发者。 - Teams evaluating Rust for performance-sensitive or safety-sensitive components
正在评估 Rust,用于性能敏感或安全敏感模块的团队。 - Readers who want a chapter order that moves from syntax and ownership into async, FFI, and migration strategy
希望按“语法与所有权 → async → FFI → 迁移策略”顺序系统推进的读者。
What You Will Learn
这本书会覆盖什么
- How Rust differs from Java in memory management, error handling, type modeling, and concurrency
Rust 在内存管理、错误处理、类型建模和并发方面与 Java 的核心差异。 - How to map Java concepts such as interfaces, records, streams,
Optional, andCompletableFutureinto Rust equivalents
如何把接口、record、Stream、Optional、CompletableFuture这些 Java 经验映射到 Rust 世界。 - How to structure real Rust projects with Cargo, crates, modules, testing, and common ecosystem tools
如何使用 Cargo、crate、模块、测试和常见生态工具来组织真实 Rust 项目。 - How to migrate gradually instead of attempting a reckless full rewrite
如何渐进式迁移,而不是头脑发热搞一次性重写。
Suggested Reading Order
建议阅读顺序
| Range 范围 | Focus 重点 | Outcome 阶段结果 |
|---|---|---|
| Chapters 1-4 第 1-4 章 | Motivation, setup, core syntax 动机、环境、基础语法 | Can read and write small Rust programs 能够读写小型 Rust 程序。 |
| Chapters 5-7 第 5-7 章 | Data modeling and ownership 数据建模与所有权 | Can explain moves, borrows, and Option能够讲清 move、borrow 和 Option。 |
| Chapters 8-10 第 8-10 章 | Project structure, errors, traits 工程结构、错误、trait | Can organize multi-file crates and design APIs 能够组织多文件 crate 并设计基础 API。 |
| Chapters 11-14 第 11-14 章 | Conversions, iterators, async, FFI, testing 转换、迭代器、async、FFI、测试 | Can build realistic services and tools 能够开始写接近真实项目的工具和服务。 |
| Chapters 15-17 第 15-17 章 | Migration, tooling, capstone 迁移、工具链、综合项目 | Can plan a Java-to-Rust adoption path 能够设计一条从 Java 过渡到 Rust 的采用方案。 |
Companion Books In This Repository
仓库里的配套教材
- Rust for C/C++ Programmers
适合需要和 C/C++ 心智做对照的读者。 - Rust for C# Programmers
适合同时关心托管运行时与 .NET 生态对照的读者。 - Rust for Python Programmers
适合还要和脚本生态做横向比较的读者。 - Async Rust: From Futures to Production
适合继续深挖异步模型与生产实践。 - Rust Patterns
适合进入高级模式与工程细节。
Table of Contents
目录
Part I — Foundations
第一部分:基础
- 1. Introduction and Motivation
1. 引言与动机 - 2. Getting Started
2. 快速开始 - 3. Built-in Types and Variables
3. 内置类型与变量 - 4. Control Flow
4. 控制流 - 5. Data Structures and Collections
5. 数据结构与集合 - 6. Enums and Pattern Matching
6. 枚举与模式匹配 - 7. Ownership and Borrowing
7. 所有权与借用 - 8. Crates and Modules
8. crate 与模块 - 9. Error Handling
9. 错误处理 - 10. Traits and Generics
10. Trait 与泛型 - 10.3 Object-Oriented Thinking in Rust
10.3 Rust 中的面向对象思维 - 11. From and Into Traits
11. From 与 Into Trait - 12. Closures and Iterators
12. 闭包与迭代器
Part II — Concurrency and Systems
第二部分:并发与系统
- 13. Concurrency
13. 并发 - 13.1 Async/Await Deep Dive
13.1 Async/Await 深入解析 - 14. Unsafe Rust and FFI
14. Unsafe Rust 与 FFI - 14.1 Testing
14.1 测试
Part III — Migration and Practice
第三部分:迁移与实践
- 15. Migration Patterns and Case Studies
15. 迁移模式与案例 - 15.1 Essential Crates for Java Developers
15.1 Java 开发者常用 crate - 15.2 Incremental Adoption Strategy
15.2 渐进式引入策略 - 15.3 Spring and Spring Boot Migration
15.3 Spring 与 Spring Boot 迁移 - 16. Best Practices and Reference
16. 最佳实践与参考 - 16.1 Performance Comparison and Migration
16.1 性能比较与迁移 - 16.2 Learning Path and Resources
16.2 学习路径与资源 - 16.3 Rust Tooling for Java Developers
16.3 面向 Java 开发者的 Rust 工具
Capstone
综合项目
- 17. Capstone Project: Migrate a Spring Boot User Service
17. 综合项目:迁移一个 Spring Boot 用户服务
Introduction and Motivation §§ZH§§ 引言与动机
The Case for Rust for Java Developers
为什么 Rust 值得 Java 开发者认真学一遍
What you’ll learn: Where Rust fits for Java teams, which JVM pain points it addresses well, and what conceptual shifts matter most in the first week.
本章将学习: Rust 适合落在 Java 团队的什么位置、它对 JVM 世界的哪些痛点特别有效,以及入门第一周最关键的思维切换。Difficulty: 🟢 Beginner
难度: 🟢 初级
Java remains an excellent language for business systems, backend APIs, large teams, and mature tooling. Rust is attractive in a different slice of the problem space: when predictable latency, low memory overhead, native deployment, and stronger compile-time guarantees start to matter more than runtime convenience.
Java 依然非常适合业务系统、后端 API、大团队协作以及成熟工具链环境。Rust 吸引人的地方在另一个问题区间:当稳定延迟、较低内存开销、原生部署,以及更强的编译期保证,比运行时便利性更重要时,Rust 的优势就会越来越明显。
Why Java Teams Look at Rust
Java 团队为什么会看 Rust
Three common triggers show up again and again:
最常见的触发点通常有三类:
- A service is stable functionally, but memory pressure and GC behavior dominate performance tuning.
业务逻辑已经稳定,但性能优化总被内存占用和 GC 行为卡住。 - A library needs to be embedded into many runtimes or shipped as a small native binary.
一个库需要嵌入多个运行时,或者需要交付成小体积原生二进制。 - A component sits close to the operating system, networking stack, storage layer, or protocol boundary where bugs are expensive.
某个模块离操作系统、网络栈、存储层或协议边界很近,出了问题代价特别高。
Performance Without the Runtime Tax
少掉运行时税之后的性能空间
// Java: excellent ergonomics, but allocations and GC shape runtime behavior.
List<Integer> values = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
values.add(i * 2);
}
#![allow(unused)]
fn main() {
// Rust: the same data structure is explicit and native.
let mut values = Vec::with_capacity(1_000_000);
for i in 0..1_000_000 {
values.push(i * 2);
}
}
Rust does not magically make every program faster. The important difference is that there is no GC, no JVM startup cost, and no hidden object model tax in the background. That makes latency and memory use easier to reason about.
Rust 不是魔法,换语言不等于所有程序都会自动提速。真正关键的区别在于:没有 GC、没有 JVM 启动负担、没有隐藏的对象模型开销。于是延迟和内存使用会更容易被分析,也更容易被控制。
Common Java Pain Points That Rust Addresses
Rust 能明显缓解的 Java 常见痛点
Nulls Become Option
空值问题会变成 Option 问题
Java reduced null pain with better tooling, annotations, and Optional, but plain references can still be null and failures still happen at runtime.
Java 这些年靠工具、注解和 Optional 已经把空值问题压下去不少,但普通引用依旧可能是 null,很多错误依旧在运行时才暴露。
String displayName(User user) {
return user.getProfile().getDisplayName().toUpperCase();
}
#![allow(unused)]
fn main() {
fn display_name(user: &User) -> Option<String> {
user.profile
.as_ref()?
.display_name
.as_ref()
.map(|name| name.to_uppercase())
}
}
In Rust, absence is represented in the type system, and callers must handle it explicitly.
在 Rust 里,“值可能不存在”会直接体现在类型里,调用方必须显式处理。
Exceptions Become Result
异常流会变成 Result 流
User loadUser(long id) throws IOException, SQLException {
// multiple hidden control-flow exits
}
#![allow(unused)]
fn main() {
fn load_user(id: u64) -> Result<User, LoadUserError> {
// all fallible paths are explicit in the signature
}
}
The gain is not just stylistic. Error flows are visible at API boundaries, which makes refactoring safer.
这不只是写法偏好问题。错误路径一旦显式出现在 API 边界,重构时心里就会更有数。
Shared Mutable State Gets Much Harder to Abuse
共享可变状态会更难被滥用
Java can absolutely do correct concurrent programming, but the compiler will not stop accidental misuse of shared mutable data structures. Rust is stricter up front so that races and aliasing mistakes are caught earlier.
Java 当然可以写出正确的并发程序,但编译器通常不会阻止误用共享可变数据。Rust 则更愿意在前面把规矩立死,让竞争条件和别名问题更早暴露出来。
When to Choose Rust Over Java
什么时候更该选 Rust 而不是 Java
Rust is often a strong fit for:
这些场景往往更适合 Rust:
- network proxies and gateways with tight latency budgets
对延迟预算非常敏感的网络代理和网关。 - command-line tools and local developer utilities
命令行工具和本地开发辅助程序。 - storage engines, parsers, protocol implementations, and agents
存储引擎、解析器、协议实现、agent 之类的基础组件。 - libraries that need to be called from Java, Python, Node.js, or C#
需要被 Java、Python、Node.js、C# 等多种语言调用的底层库。 - edge, embedded, and container-heavy deployments where binary size matters
边缘、嵌入式或容器密集环境里,对二进制体积和资源占用有要求的场景。
Java is often still the better fit for:
这些场景往往还是 Java 更顺手:
- mainstream enterprise CRUD systems
主流企业 CRUD 系统。 - large teams already optimized around Spring, Jakarta EE, or the JVM ecosystem
已经深度围绕 Spring、Jakarta EE 或 JVM 生态组织起来的大团队。 - products where rapid iteration and operational familiarity matter more than native efficiency
产品节奏更看重快速迭代和运维熟悉度,而不是原生效率。
Language Philosophy Comparison
语言哲学对照
| Topic 主题 | Java | Rust |
|---|---|---|
| Memory 内存 | GC-managed heap GC 管理的堆 | Ownership and borrowing 所有权与借用 |
| Nullability 空值 | Convention, annotations, Optional约定、注解、 Optional | Option<T> in the type systemOption<T> 进入类型系统 |
| Errors 错误 | Exceptions 异常 | Result<T, E> |
| Inheritance 复用方式 | Classes and interfaces 类与接口 | Traits and composition trait 与组合 |
| Concurrency 并发 | Threads, executors, futures 线程、执行器、future | Threads, async runtimes, Send and Sync线程、异步运行时、 Send 与 Sync |
| Deployment 部署 | JVM process or native image JVM 进程或 native image | Native binary by default 默认原生二进制 |
The core mental shift is this: Java asks the runtime to keep the system safe and live. Rust asks the type system to prove more invariants before the program is allowed to run.
最关键的思维切换是这一句:Java 倾向于把“安全和存活”交给运行时兜底,Rust 则倾向于在程序运行前先让类型系统证明更多约束。
Quick Reference: Rust vs Java
Rust 与 Java 快速对照
| Java concept Java 概念 | Rust concept Rust 对应物 |
|---|---|
interface | trait |
record | struct plus trait implsstruct 加上一组 trait 实现 |
Optional<T> | Option<T> |
| checked and unchecked exceptions 受检与非受检异常 | Result<T, E> |
Stream<T> | iterator adapters 迭代器适配器链 |
CompletableFuture<T> | Future<Output = T> |
| Maven or Gradle module Maven 或 Gradle 模块 | crate |
| package visibility 包级可见性 | pub, pub(crate), module privacypub、pub(crate) 与模块私有性 |
The rest of the book expands each row of this table until the mapping stops feeling abstract.
后面的章节就是把这张对照表一行一行拆开讲,直到这些映射关系不再停留在纸面上。
Getting Started §§ZH§§ 快速开始
Getting Started
快速开始
What you’ll learn: How to install Rust, create the first project, and map the Cargo workflow to what Java developers know from Maven or Gradle.
本章将学习: 如何安装 Rust、创建第一个项目,以及怎样把 Cargo 工作流和 Java 开发者熟悉的 Maven、Gradle 对上号。Difficulty: 🟢 Beginner
难度: 🟢 初级
Install the Toolchain
安装工具链
Use rustup to install Rust and manage toolchains:
使用 rustup 安装 Rust 并管理工具链:
winget install Rustlang.Rustup
rustup default stable
rustc --version
cargo --version
The Java analogy is a mix of JDK installer plus SDK manager, except Rust keeps the toolchain story much tighter.
如果拿 Java 类比,这有点像把 JDK 安装器和 SDK 管理器揉到了一起,只是 Rust 的工具链故事要紧凑得多。
Create the First Project
创建第一个项目
cargo new hello-rust
cd hello-rust
cargo run
That single command sequence creates the project, compiles it, and runs it.
这一串命令会把项目创建、编译和运行一次性做完。
Cargo vs Maven / Gradle
Cargo 与 Maven / Gradle 对照
| Activity 活动 | Java habit Java 习惯 | Rust habit Rust 习惯 |
|---|---|---|
| initialize project 初始化项目 | gradle init or archetype | cargo new |
| compile 编译 | mvn package or gradle build | cargo build |
| run tests 运行测试 | mvn test or gradle test | cargo test |
| run app 运行程序 | plugin task | cargo run |
| add dependency 添加依赖 | edit build file | cargo add crate_name |
First Program
第一个程序
fn main() {
println!("Hello, Rust!");
}
There is no class wrapper, no public static void main, and no object ceremony around the entry point.
这里没有类壳子、没有 public static void main,也没有围绕入口点的对象仪式感。
Reading Arguments
读取参数
fn main() {
let args: Vec<String> = std::env::args().collect();
println!("{args:?}");
}
For anything beyond trivial parsing, use clap.
只要参数解析稍微复杂一点,就上 clap。
The Three Commands to Memorize
先记住这三个命令
cargo check
cargo test
cargo run
cargo check is especially valuable for new Rust developers because it gives fast feedback without producing a final binary.
对刚学 Rust 的人来说,cargo check 特别值钱,因为它不用真的产出最终二进制,就能给出很快的反馈。
Advice
建议
- Install
rust-analyzerin the editor immediately.
编辑器里第一时间装上rust-analyzer。 - Prefer
cargo checkduring rapid iteration.
快速迭代阶段优先跑cargo check。 - Keep the first project small; ownership is easier to learn on tiny programs.
第一个项目尽量做小,所有权在小程序里更容易学明白。
Once Cargo and the compiler stop feeling foreign, the rest of Rust becomes much easier to approach.
只要 Cargo 和编译器不再显得陌生,后面的 Rust 学习难度就会明显往下掉。
Essential Keywords Reference (optional) §§ZH§§ 核心关键字速查表(可选)
Essential Keywords Reference
核心关键字速查表
What you’ll learn: A compact keyword map for Java developers so Rust syntax stops looking alien during the first few chapters.
本章将学习: 给 Java 开发者准备的一份紧凑关键字映射表,让 Rust 语法在前几章里别再显得那么陌生。Difficulty: 🟢 Beginner
难度: 🟢 初级
This chapter is a quick reference, not a replacement for the conceptual chapters.
这一章是速查表,不是概念章节的替代品。
| Rust keyword | Rough Java analogy | What it usually means |
|---|---|---|
let | local variable declaration 局部变量声明 | bind a value to a name 把一个值绑定到名字上 |
mut | mutable local variable 可变局部变量 | allow reassignment or mutation 允许重绑定或修改 |
fn | method or function declaration 方法或函数声明 | define a function 定义函数 |
struct | class or record shell 类或 record 外壳 | define a data type with fields 定义带字段的数据类型 |
enum | enum plus sealed hierarchy 枚举加 sealed 层级 | tagged union with variants 带分支的代数数据类型 |
impl | method block 方法实现块 | attach methods or trait impls 挂方法或 trait 实现 |
trait | interface | shared behavior contract 行为契约 |
match | switch expression switch 表达式 | exhaustive pattern matching 穷尽模式匹配 |
if let | guarded destructuring 条件解构 | handle one successful pattern 处理一个匹配成功的分支 |
while let | loop while match succeeds 匹配成功时循环 | consume values until pattern stops matching 持续处理直到模式失配 |
pub | public visibility 公开可见性 | expose outside the module 向模块外暴露 |
crate | module or artifact root 模块或产物根 | current package boundary 当前包边界 |
use | import | bring names into scope 把名字引入当前作用域 |
mod | package or nested module 包或嵌套模块 | declare a module 声明模块 |
ref | bind by reference in a pattern 在模式里按引用绑定 | avoid moving during pattern matching 模式匹配时避免 move |
move | capture by value 按值捕获 | transfer ownership into closure or thread 把所有权带进闭包或线程 |
async | async method marker 异步方法标记 | function returns a future 函数返回 future |
await | future completion point future 完成点 | suspend until result is ready 挂起直到结果就绪 |
unsafe | dangerous low-level block 低层危险块 | programmer must uphold invariants 开发者自己维持约束 |
where | generic bounds clause 泛型约束子句 | move trait bounds out of angle brackets 把约束挪出尖括号 |
Self | current class type 当前类型 | current implementing type 当前实现类型 |
dyn | interface reference 接口引用 | dynamic dispatch through a trait object 通过 trait object 动态分发 |
const | compile-time constant 编译期常量 | inlined immutable value 被内联的不可变值 |
static | static field 静态字段 | process-wide storage 进程级存储 |
Three Keywords That Need Extra Attention
三个需要额外留神的关键字
mut
mut
Mutability is explicit on the binding:
可变性直接写在绑定上:
#![allow(unused)]
fn main() {
let x = 1;
let mut y = 2;
y += 1;
}
match
match
match is not just a switch statement. It is a pattern-matching expression and must usually cover every case.match 不只是 switch 语句,它是模式匹配表达式,而且通常要求覆盖所有情况。
move
move
Java developers often underestimate move. In Rust it matters whenever values enter closures, threads, or async tasks.
很多 Java 开发者会低估 move 的重要性。只要值要进入闭包、线程或异步任务,它就立刻变得关键。
Keep this table nearby during the first pass through the book. After a few chapters, most of these keywords become second nature.
前几章先把这张表放在手边。读上几章之后,大多数关键字就会自然顺下来了。
Built-in Types and Variables §§ZH§§ 内置类型与变量
Built-in Types and Variables
内置类型与变量
What you’ll learn: How Rust primitives, strings, mutability, and conversions differ from Java’s type model.
本章将学习: Rust 的基础类型、字符串、可变性和类型转换,分别和 Java 的类型模型有哪些差别。Difficulty: 🟢 Beginner
难度: 🟢 初级
Rust looks familiar at first because it has integers, booleans, strings, and variables. The differences start showing up once ownership, mutability, and explicit conversions enter the picture.
Rust 乍一看不难认,因为它也有整数、布尔、字符串和变量。真正的差异会在所有权、可变性和显式转换这些地方开始冒出来。
Primitive Types
基础类型
| Java | Rust | Notes |
|---|---|---|
int | i32 | explicit width 位宽显式 |
long | i64 | explicit width 位宽显式 |
double | f64 | default floating-point choice 默认浮点选择 |
boolean | bool | same general role 职责类似 |
char | char | Unicode scalar value, not UTF-16 code unit Unicode 标量值,不是 UTF-16 单元 |
byte | u8 or i8 | choose signedness explicitly 显式区分有无符号 |
Rust forces width and signedness into the spelling. That removes guesswork at API boundaries.
Rust 把位宽和有无符号直接写进类型名里,这样 API 边界上的歧义会少很多。
Variables and Mutability
变量与可变性
#![allow(unused)]
fn main() {
let name = "Ada";
let mut count = 0;
count += 1;
}
Bindings are immutable by default. This is one of the earliest places where Rust asks for more explicit intent than Java.
绑定默认不可变,这是 Rust 最早要求“意图必须写清楚”的地方之一。
Shadowing
遮蔽
#![allow(unused)]
fn main() {
let port = "8080";
let port: u16 = port.parse().unwrap();
}
Shadowing lets a name be rebound with a new type or refined value. It is often cleaner than introducing parsedPort-style names everywhere.
遮蔽允许同一个名字重新绑定成更精确的值或新类型。很多时候,它比到处起 parsedPort 这种名字更清爽。
String vs &str
String 与 &str
This is the first string distinction Java developers must really learn.
这是 Java 开发者必须认真吃透的第一个字符串差异。
| Rust type | Rough Java intuition | Meaning |
|---|---|---|
String | owned String拥有所有权的字符串 | heap-allocated, owned text 堆分配、拥有所有权 |
&str | read-only string view 只读字符串视图 | borrowed string slice 借用的字符串切片 |
If a function only needs to read text, prefer &str.
如果函数只是读取文本,优先用 &str。
Formatting and Printing
格式化与打印
#![allow(unused)]
fn main() {
let name = "Ada";
let score = 42;
println!("{name} scored {score}");
}
Rust formatting uses macros rather than overloaded println methods.
Rust 的格式化基于宏,不是靠一堆重载打印方法。
Explicit Conversions
显式转换
Rust avoids many implicit numeric conversions:
Rust 会尽量避免隐式数值转换:
#![allow(unused)]
fn main() {
let x: i32 = 10;
let y: i64 = x as i64;
}
That can feel verbose at first, but it reduces accidental widening and narrowing.
刚开始会觉得啰嗦,但它确实减少了很多无意间的扩大和截断。
Advice
建议
- Use immutable bindings unless mutation is genuinely needed.
除非真的要改,否则先用不可变绑定。 - Prefer
&strfor input parameters andStringfor owned returned text.
输入参数优先&str,需要返回拥有所有权的文本时再用String。 - Read the type annotations in compiler diagnostics carefully; they are often the fastest way to learn.
编译器诊断里的类型信息要认真看,那往往是学得最快的入口。
This chapter is where the surface syntax still feels easy. The harder conceptual shift begins when values start moving rather than merely being referenced.
这一章还只是表层语法,整体不算难。真正更硬的思维切换,会从“值开始 move,而不是只是被引用”那里开始。
True Immutability vs Record Illusions §§ZH§§ 真正的不可变性与 record 幻觉
True Immutability vs Record Illusions
真正的不可变性与 record 幻觉
What you’ll learn: Why Java records are useful but not deeply immutable by default, and how Rust’s default immutability changes the design conversation.
本章将学习: 为什么 Java record 很有用,但默认并不等于深度不可变,以及 Rust 的默认不可变性会怎样改变设计讨论。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Java records reduce boilerplate, but they do not automatically guarantee deep immutability.
Java record 确实能大幅减少样板代码,但它并不会自动保证深度不可变。
The Java Record Caveat
Java record 的限制
record UserProfile(String name, List<String> tags) {}
The tags reference is final, but the list behind it can still mutate unless the code deliberately wraps or copies it.
这里 tags 引用本身是 final,但后面的列表内容依然可能变化,除非显式包裹或拷贝。
Rust’s Default Position
Rust 的默认立场
#![allow(unused)]
fn main() {
struct UserProfile {
name: String,
tags: Vec<String>,
}
}
If the binding is immutable, mutation is blocked unless a mutable binding or a special interior mutability type is involved.
如果绑定本身不可变,那么修改动作就会被拦住,除非显式使用可变绑定,或者引入内部可变性类型。
What This Means in Practice
落到实践里的差别
| Concern 关注点 | Java record | Rust struct |
|---|---|---|
| shallow immutability 浅层不可变 | common 常见 | common 常见 |
| deep immutability 深度不可变 | manual design choice 靠设计保证 | manual design choice 也靠设计保证 |
| mutation signal 修改信号 | often hidden behind references 常藏在引用背后 | explicit through mut or interior mutability通过 mut 或内部可变性显式出现 |
Rust does not magically make every data structure deeply immutable, but it makes mutation far easier to spot.
Rust 也不会魔法般地让所有数据结构都变成深度不可变,但它确实让“哪里会变”更容易被看出来。
Design Guidance
设计建议
- treat Java records as concise carriers, not as proof of immutability
把 Java record 当成简洁的数据载体,不要把它误当成不可变证明。 - in Rust, start immutable and add
mutonly where required
在 Rust 里先从不可变开始,再在必要处加mut。 - if mutation must cross shared boundaries, make that choice obvious in the type design
如果可变状态必须跨共享边界存在,就让这种选择在类型设计里足够显眼。
The useful lesson is not “records are bad.” The useful lesson is that Rust defaults push teams toward more explicit state transitions.
真正有价值的结论不是“record 不好”,而是 Rust 的默认值会把团队推向更明确的状态转换设计。
Control Flow §§ZH§§ 控制流
Control Flow
控制流
What you’ll learn: How Rust control flow resembles Java in shape but differs in one crucial way: many constructs are expressions, not just statements.
本章将学习: Rust 控制流在外形上和 Java 很像,但有个关键差异:很多结构是表达式,不只是语句。Difficulty: 🟢 Beginner
难度: 🟢 初级
Java developers usually adapt to Rust control flow quickly. The biggest surprise is that Rust uses expressions much more aggressively.
Java 开发者通常很快就能适应 Rust 的控制流。真正让人一下子拧巴的,往往是 Rust 对表达式的使用要激进得多。
if as an Expression
把 if 当表达式
#![allow(unused)]
fn main() {
let label = if score >= 90 { "great" } else { "ok" };
}
That is closer to a Java ternary expression than to a plain if statement.
这更接近 Java 里的三元表达式,而不是普通 if 语句。
match
match
#![allow(unused)]
fn main() {
let text = match status {
200 => "ok",
404 => "missing",
_ => "other",
};
}
match is central in Rust because it works with enums, options, results, and destructuring.match 在 Rust 里特别核心,因为它能同时覆盖 enum、option、result 和解构场景。
Loops
循环
| Java | Rust |
|---|---|
while (...) | while condition { ... } |
enhanced for | for item in items { ... } |
for (;;) | loop { ... } |
loop is the dedicated infinite-loop construct.loop 是专门的无限循环结构。
Early Exit
提前退出
Rust has return, break, and continue as expected. It also lets break return a value from loop.
Rust 当然也有 return、break、continue,但它还允许 break 从 loop 里直接带值出来。
#![allow(unused)]
fn main() {
let result = loop {
if ready() {
break 42;
}
};
}
Pattern-Oriented Flow
面向模式的控制流
#![allow(unused)]
fn main() {
if let Some(user) = maybe_user {
println!("{}", user.name);
}
}
This is a very common replacement for “null check plus cast plus use” style logic.
这在 Rust 里非常常见,经常用来替代“先判空、再取值、再使用”的流程。
Advice
建议
- remember that
if,match, and evenloopcan produce values
记住if、match、甚至loop都可能产出值。 - reach for
matchwhen branching on enums or structured data
只要是在 enum 或结构化数据上分支,就优先考虑match。 - prefer readable control flow over clever one-liners
可读的控制流比自作聪明的一行流更重要。
Rust control flow is not hard. The main adjustment is learning to think in expressions and patterns rather than in statements alone.
Rust 控制流本身并不难。真正需要适应的是:思考方式要从“纯语句”转向“表达式和模式”。
Data Structures and Collections §§ZH§§ 数据结构与集合
Data Structures and Collections
数据结构与集合
What you’ll learn: How Rust models data with tuples, arrays, slices, structs, and standard collections, and how those choices compare to Java classes and collection interfaces.
本章将学习: Rust 如何用元组、数组、切片、结构体和标准集合来建模数据,以及这些选择和 Java 的 class、集合接口体系有什么差别。Difficulty: 🟢 Beginner
难度: 🟢 初级
Rust data modeling is lighter than Java’s object-oriented default. There is less ceremony, but also less hidden behavior.
Rust 的数据建模比 Java 默认的面向对象风格更轻。仪式感更少,隐藏行为也更少。
Tuples
元组
#![allow(unused)]
fn main() {
let pair = ("Ada", 42);
let (name, score) = pair;
}
Tuples are useful for temporary groupings. If the fields need names, move to a struct.
元组适合临时打包几个值。如果字段需要名字,就该升级成 struct 了。
Arrays and Slices
数组与切片
| Java | Rust |
|---|---|
int[] | [i32; N] |
| array view or subrange 数组视图或子区间 | &[i32] |
An array in Rust has length as part of its type. A slice is the borrowed view over contiguous elements.
Rust 数组会把长度写进类型里。slice 则是对连续元素的一段借用视图。
Structs vs Classes
结构体与类
#![allow(unused)]
fn main() {
struct User {
id: u64,
name: String,
}
}
Rust structs hold data. Methods live separately in impl blocks. There is no hidden inheritance tree around them.
Rust struct 主要承载数据,方法放在单独的 impl 块里。它背后没有一整棵默认继承树跟着跑。
Standard Collections
标准集合
| Java | Rust |
|---|---|
List<T> | Vec<T> |
Map<K, V> | HashMap<K, V> |
Set<T> | HashSet<T> |
Rust standard collections are concrete types rather than interface-first abstractions.
Rust 标准集合更偏向具体类型,而不是先上接口再谈实现。
Why This Matters
这意味着什么
Java code often starts with interfaces and containers. Rust code often starts with concrete data structures and only introduces abstraction when the need becomes real.
Java 代码经常从接口和容器开始,Rust 代码则更常从具体数据结构起步,等抽象需求真的出现时再加一层。
Advice
建议
- use tuples for short-lived grouped values
短生命周期的分组值用 tuple。 - use structs for domain data
领域数据优先用 struct。 - use slices for read-only borrowed views into arrays or vectors
只读借用视图优先用 slice。 - begin with
VecandHashMap; optimize later if the workload demands it
先把Vec和HashMap用顺,真的有性能压力再优化。
Rust’s data model is simple on purpose. That simplicity is one of the reasons ownership stays tractable.
Rust 的数据模型是故意做得简单,这种简单也是所有权还能维持可理解性的原因之一。
Constructor Patterns §§ZH§§ 构造器模式
Constructor Patterns
构造器模式
What you’ll learn: How Rust replaces Java constructors with associated functions,
Default, and builders.
本章将学习: Rust 如何用关联函数、Default和 builder 模式,替代 Java 风格的构造器。Difficulty: 🟢 Beginner
难度: 🟢 初级
Rust does not have constructors in the Java sense. Instead, types usually expose associated functions such as new.
Rust 没有 Java 意义上的构造器,类型通常通过关联函数来暴露初始化入口,最常见的名字就是 new。
A Basic new
一个基本的 new
#![allow(unused)]
fn main() {
struct Config {
host: String,
port: u16,
}
impl Config {
fn new(host: String, port: u16) -> Self {
Self { host, port }
}
}
}
This is explicit and boring, which is usually a good thing.
这种写法非常直白,也有点朴素,但大多数时候这恰恰是好事。
Default
Default
#![allow(unused)]
fn main() {
#[derive(Default)]
struct RetryPolicy {
max_retries: u32,
}
}
Default is a natural fit for types that have sensible baseline values.
只要一个类型存在合理的默认值,Default 就很合适。
Builder Pattern
builder 模式
Builders are useful when:
下面这些情况就适合上 builder:
- there are many optional fields
可选字段很多。 - construction needs validation
构造过程需要校验。 - call sites should read like configuration
调用点希望看起来像配置声明。
#![allow(unused)]
fn main() {
struct ClientBuilder {
timeout_ms: u64,
}
impl ClientBuilder {
fn new() -> Self {
Self { timeout_ms: 1000 }
}
fn timeout_ms(mut self, timeout_ms: u64) -> Self {
self.timeout_ms = timeout_ms;
self
}
}
}
Guidance
选择建议
- use
newfor ordinary construction
普通初始化用new。 - use
Defaultfor sensible zero-argument initialization
零参数初始化合理时,用Default。 - use builders when option count and readability demand them
可选项太多、又想保证可读性时,用 builder。
Rust construction is less magical than Java frameworks, but the trade-off is simpler reasoning at call sites.
Rust 的构造方式没有 Java 框架那种魔法感,但换来的好处是:调用点更容易读,也更容易推理。
Collections — Vec, HashMap, and Iterators §§ZH§§ 集合:Vec、HashMap 与迭代器
Collections: Vec, HashMap, and Iterators
集合:Vec、HashMap 与迭代器
What you’ll learn: How the most common Rust collections compare to Java’s
List,Map, and stream-based traversal patterns.
本章将学习: Rust 最常用的集合类型,分别如何对应 Java 的List、Map和基于 Stream 的遍历模式。Difficulty: 🟢 Beginner
难度: 🟢 初级
Vec<T> vs List<T>
Vec<T> 与 List<T>
Vec<T> is the workhorse collection in Rust.Vec<T> 是 Rust 里最常干活的集合类型。
#![allow(unused)]
fn main() {
let mut numbers = vec![1, 2, 3];
numbers.push(4);
}
If Java developers are tempted to ask “what is the interface type here?”, the answer is usually “there isn’t one yet, because the concrete vector is enough.”
如果脑子里冒出来“这里的接口类型是什么”,Rust 的常见答案往往是:“暂时没有,因为具体的 Vec 已经够用了。”
HashMap<K, V> vs Map<K, V>
HashMap<K, V> 与 Map<K, V>
#![allow(unused)]
fn main() {
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert("ada", 98);
scores.insert("grace", 100);
}
Lookups return Option<&V> rather than null.
查找结果返回的是 Option<&V>,不是 null。
Iteration
迭代
#![allow(unused)]
fn main() {
for value in &numbers {
println!("{value}");
}
}
Rust makes ownership visible during iteration:
Rust 会在迭代时把所有权意图直接摆出来:
iter()borrows itemsiter()借用元素。iter_mut()mutably borrows itemsiter_mut()可变借用元素。into_iter()consumes the collectioninto_iter()消耗整个集合。
That third case is where many Java developers first feel the ownership model in collection code.
第三种情况往往是很多 Java 开发者第一次在集合代码里真正感受到所有权模型的地方。
Iterators vs Streams
迭代器与 Stream
| Java Stream | Rust iterator |
|---|---|
| lazy pipeline 惰性流水线 | lazy pipeline 惰性流水线 |
| terminal operation required 需要终止操作 | terminal operation required 需要终止操作 |
| often object-heavy 常常对象味更重 | often zero-cost and monomorphized 通常零成本、单态化 |
#![allow(unused)]
fn main() {
let doubled: Vec<_> = numbers
.iter()
.map(|n| n * 2)
.collect();
}
Advice
建议
- start with
Vecbefore searching for more abstract collection models
先把Vec用明白,再考虑更抽象的集合模型。 - use
Option-aware lookups rather than assuming missing values are exceptional
查找缺失值时顺着Option去处理,不要先把它想成异常。 - choose
iter,iter_mut, orinto_iterbased on ownership intent
根据所有权意图去选iter、iter_mut还是into_iter。
Once these three collection patterns click, a large amount of day-to-day Rust code becomes readable.
只要这三类集合模式顺下来,大量日常 Rust 代码就开始变得好读了。
Enums and Pattern Matching §§ZH§§ 枚举与模式匹配
Enums and Pattern Matching
枚举与模式匹配
What you’ll learn: How Java’s
sealed interface,record, andswitchexpressions map to Rustenumandmatch, and when a closed domain should be modeled as an enum instead of a hierarchy.
本章将学习: Java 里的sealed interface、record、switch表达式如何迁到 Rust 的enum与match,以及什么时候应该把封闭领域建模成枚举,而不是继承层级。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Java developers often reach for classes and interfaces when a domain has a few fixed variants.
当一个领域只有少数几种固定变体时,Java 开发者通常会本能地想到 class 和 interface。
Rust’s default answer is different: if the set of cases is closed, define an enum and let match force complete handling.
Rust 的默认答案不一样:如果变体集合是封闭的,就定义 enum,再让 match 强制把所有情况处理完整。
The Familiar Java Shape
Java 里熟悉的建模方式
public sealed interface PaymentCommand
permits Charge, Refund, Cancel { }
public record Charge(String orderId, long cents) implements PaymentCommand { }
public record Refund(String paymentId, long cents) implements PaymentCommand { }
public record Cancel(String orderId) implements PaymentCommand { }
public final class PaymentService {
public String handle(PaymentCommand command) {
return switch (command) {
case Charge charge -> "charge " + charge.orderId();
case Refund refund -> "refund " + refund.paymentId();
case Cancel cancel -> "cancel " + cancel.orderId();
};
}
}
This is already the better side of modern Java, but the model is still spread across multiple declarations.
这已经是现代 Java 里相对更好的写法了,但模型依然分散在多个声明里。
The Native Rust Shape
Rust 原生的写法
#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
enum PaymentCommand {
Charge { order_id: String, cents: u64 },
Refund { payment_id: String, cents: u64 },
Cancel { order_id: String },
}
fn handle(command: PaymentCommand) -> String {
match command {
PaymentCommand::Charge { order_id, cents } => {
format!("charge {order_id} for {cents} cents")
}
PaymentCommand::Refund { payment_id, cents } => {
format!("refund {payment_id} for {cents} cents")
}
PaymentCommand::Cancel { order_id } => {
format!("cancel order {order_id}")
}
}
}
}
Rust keeps the whole closed set in one place, which makes the model easier to audit and change.
Rust 会把整个封闭集合放在一处定义,阅读、审查和修改都会更直接。
Two consequences matter a lot:
这里有两个很关键的后果:
- the full set of variants is visible at once
所有变体一眼就能看全。 - adding a new variant forces every relevant
matchto be revisited
一旦新增变体,所有相关match都会被编译器强制重新检查。
match Is More Than a Safer switch
match 不只是更安全的 switch
Rust match brings four habits that Java teams quickly learn to rely on:
Rust 的 match 会带来四种很快就离不开的习惯:
- exhaustive handling
穷尽处理。 - pattern destructuring
模式解构。 - guards with
if
带if的守卫。 - expression-oriented branching
以表达式为中心的分支返回。
#![allow(unused)]
fn main() {
#[derive(Debug)]
enum UserEvent {
SignedUp { user_id: u64, email: String },
LoginFailed { user_id: u64, attempts: u32 },
SubscriptionChanged { plan: String, seats: u32 },
}
fn describe(event: UserEvent) -> String {
match event {
UserEvent::SignedUp { user_id, email } => {
format!("user {user_id} signed up with {email}")
}
UserEvent::LoginFailed { user_id, attempts } if attempts >= 3 => {
format!("user {user_id} is locked after {attempts} failures")
}
UserEvent::LoginFailed { user_id, attempts } => {
format!("user {user_id} failed login attempt {attempts}")
}
UserEvent::SubscriptionChanged { plan, seats } => {
format!("subscription moved to {plan} with {seats} seats")
}
}
}
}
The guard branch is the kind of thing Java developers often write as a type check followed by nested if logic.
上面这个 guard 分支,正是 Java 开发者经常会写成“类型判断 + 里层 if”的那类逻辑。
Destructuring Replaces Boilerplate Getters
解构会取代很多样板 getter
if (command instanceof Charge charge) {
return charge.orderId() + ":" + charge.cents();
}
#![allow(unused)]
fn main() {
fn audit(command: &PaymentCommand) -> String {
match command {
PaymentCommand::Charge { order_id, cents } => {
format!("charge:{order_id}:{cents}")
}
PaymentCommand::Refund { payment_id, cents } => {
format!("refund:{payment_id}:{cents}")
}
PaymentCommand::Cancel { order_id } => {
format!("cancel:{order_id}")
}
}
}
}
In Rust, unpacking the payload at the point of use is the natural style, not an advanced trick.
在 Rust 里,在哪里使用,就在哪里把载荷拆开,这是自然写法,不是什么高级技巧。
Option and Result Are the Same Modeling Idea
Option 和 Result 其实是同一套建模思想
Java teams often learn sum types as a special topic, but Rust applies the same idea to ordinary absence and failure.
Java 团队经常把 sum type 当成专题知识来看,但 Rust 会把同样的思想直接用于“值可能不存在”和“调用可能失败”这些日常场景。
#![allow(unused)]
fn main() {
fn maybe_discount(code: &str) -> Option<u8> {
match code {
"VIP" => Some(20),
"WELCOME" => Some(10),
_ => None,
}
}
fn parse_port(raw: &str) -> Result<u16, String> {
raw.parse::<u16>()
.map_err(|_| format!("invalid port: {raw}"))
}
}
After a while, enum stops feeling like a separate chapter and becomes the default tool for domain states, optional data, and typed failures.
学到后面,enum 就不会再像一章单独知识点,而会变成建模状态、可选值、类型化失败时的默认工具。
Enum or Trait?
该用 Enum 还是 Trait
| Situation 场景 | Better Rust tool 更适合的 Rust 工具 | Why 原因 |
|---|---|---|
| fixed set of domain states 固定的领域状态集合 | enum | compiler can enforce completeness 编译器可以强制完整处理 |
| plugin-style extension 插件式可扩展实现 | trait | downstream code may add implementations 下游代码以后还能新增实现 |
| commands or events across a boundary 跨边界的命令或事件 | enum | easy to serialize and match 更适合序列化和匹配 |
| shared behavior over unrelated types 多个无关类型共享行为 | trait | behavior is the changing axis 变化轴是行为,不是状态集合 |
If the team already knows every variant today, enum is usually the honest model.
如果团队今天就已经知道所有变体是什么,enum 往往就是更诚实的模型。
Migration Example: Order State
迁移示例:订单状态
public enum OrderStatus {
PENDING, PAID, SHIPPED, CANCELLED
}
In Java, the trouble starts when only some states need extra data, and then nullable fields begin to spread.
在 Java 里,麻烦通常从“只有部分状态需要额外数据”开始,然后各种可空字段慢慢扩散。
#![allow(unused)]
fn main() {
#[derive(Debug)]
enum OrderState {
Pending,
Paid { receipt_id: String },
Shipped { tracking_number: String },
Cancelled { reason: String },
}
fn can_refund(state: &OrderState) -> bool {
match state {
OrderState::Paid { .. } => true,
OrderState::Shipped { .. } => false,
OrderState::Cancelled { .. } => false,
OrderState::Pending => false,
}
}
}
The data lives exactly on the variant that owns it.
数据会准确地挂在真正拥有它的那个变体上。
Common Mistakes
常见误区
- using
structplus a manualkind: Stringfield
用struct再手写一个kind: String字段冒充变体。 - rebuilding abstract base classes for a closed domain
明明是封闭领域,还去重建抽象基类体系。 - adding wildcard arms too early
太早加通配分支,直接把穷尽检查价值打掉。 - storing optional fields that belong to only one case
把只属于某一种状态的字段做成一堆Option塞在公共结构里。
Exercises
练习
🏋️ Exercise: Replace a Java Sealed Hierarchy 练习:把 Java sealed 层级改写成 Rust 枚举
Model a billing workflow with these cases:
请用下面这些状态建模一个账单流程:
Draft
草稿。Issued { invoice_id: String, total_cents: u64 }
已开单,带账单号和金额。Paid { invoice_id: String, paid_at: String }
已支付,带支付时间。Failed { invoice_id: String, reason: String }
失败,带失败原因。
Then write these functions:
然后实现下面几个函数:
fn status_label(state: &BillingState) -> &'static str
返回状态标签。fn can_send_receipt(state: &BillingState) -> bool
判断是否可以发送回执。fn invoice_id(state: &BillingState) -> Option<&str>
返回可选的账单号。
Exhaustive Matching and Null Safety §§ZH§§ 穷尽匹配与空安全
Exhaustive Matching and Null Safety
穷尽匹配与空安全
What you’ll learn: Why
Option<T>and exhaustivematchmatter so much to developers coming from Java’s null-heavy past, and how Rust turns absence and branching into ordinary type design instead of defensive programming.
本章将学习: 为什么Option<T>和穷尽match对从 Java 空值历史里走出来的开发者会这么重要,以及 Rust 怎样把“值不存在”和“分支处理”变成正常的类型设计,而不是一层层防御式编程。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Rust treats absence as a first-class type problem, not as a convention problem.
Rust 把“值不存在”当成类型问题来处理,而不是只靠约定来兜。
Option<T>
Option<T>
#![allow(unused)]
fn main() {
fn find_user(id: u64) -> Option<User> {
// ...
}
}
That return type forces callers to think about “found” and “not found” explicitly.
这个返回类型会强迫调用方显式面对“找到”和“没找到”两种情况。
Why This Feels Different from Java
为什么这和 Java 体感不一样
Java has Optional<T>, but it is mostly used at API boundaries, and ordinary references can still be null. In many codebases, Optional is avoided in fields, serialization models, or older service layers. Rust uses Option<T> in ordinary APIs, so absence handling becomes routine instead of exceptional.
Java 虽然有 Optional<T>,但它更多出现在 API 边界,普通引用照样可能是 null。很多老代码库里,字段、序列化模型、旧服务层甚至都不怎么用 Optional。Rust 则会把 Option<T> 大量用在常规 API 里,于是“处理不存在”变成日常,而不是额外补丁。
That means Rust developers stop asking “should this be nullable?” and start asking “what shape of value describes reality?”
这意味着 Rust 开发里,思路会从“这里要不要可空”转成“什么样的值形态才能准确表达现实状态”。
Optional<T> Versus Option<T>
Optional<T> 与 Option<T> 的差别
For Java developers, the mental shift is important:
对 Java 开发者来说,这里的思维转换非常关键:
- Java
Optional<T>is often advisory
Java 的Optional<T>很多时候更像建议。 - Rust
Option<T>is structural
Rust 的Option<T>则是结构本身。 - Java still allows
nullto bypass the model
Java 里的null仍然可以绕过建模。 - Rust safe code does not let absence hide outside the model
Rust 的安全代码不会让“值不存在”偷偷躲出模型之外。
In practice, Option<T> is closer to a language-wide discipline than to a convenience wrapper.
实际感受上,Option<T> 更像是一种贯穿语言层面的纪律,而不只是一个顺手的包装器。
Exhaustive match
穷尽 match
#![allow(unused)]
fn main() {
match maybe_user {
Some(user) => println!("{}", user.name),
None => println!("not found"),
}
}
Missing a branch is usually a compile error rather than a runtime surprise.
漏掉一个分支,通常会变成编译错误,而不是运行时惊喜。
More Than Null Checks
这远不只是空值判断
Exhaustive matching becomes even more powerful when the type is not just “present or absent” but a real domain model:
当类型表达的已经不只是“有值还是没值”,而是真实的领域状态时,穷尽匹配的力量会更明显:
#![allow(unused)]
fn main() {
enum PaymentMethod {
Card(CardInfo),
BankTransfer(BankInfo),
Cash,
}
}
When a new variant is added, existing match expressions become incomplete until the logic is updated. That is a very different safety story from a Java switch over strings or ad-hoc discriminator values.
一旦新增了变体,现有的 match 表达式就会立刻变成不完整,直到逻辑补齐为止。这和 Java 里基于字符串或者临时判别字段的 switch,安全故事完全不是一个级别。
Why Java Teams Notice This Early
为什么 Java 团队会很早注意到这一点
Java developers often come from codebases with some combination of:
很多 Java 开发者所在的代码库,通常多少都会混着下面这些情况:
- nullable entity fields
实体字段可空。 Optionalat service boundaries
服务边界上有Optional。switchbranches that quietly miss new statesswitch悄悄漏掉新状态。- defensive
if (x != null)checks repeated everywhere
到处重复出现防御式if (x != null)。
Rust cuts through that clutter by making the state model explicit first.
Rust 的办法很干脆:先把状态模型写明白,再谈逻辑怎么处理。
Practical Benefits
实践收益
- no accidental null dereference in normal safe code
普通安全代码里不会随手踩出空指针解引用。 - branching logic is visible in one place
分支逻辑会集中出现在一个地方。 - new enum variants force old logic to be revisited
enum 新增分支时,旧逻辑会被编译器逼着重新检查。 - domain transitions become easier to review because the type tells the story
领域状态迁移会更容易审查,因为类型本身就在讲故事。
For Java developers, this is one of the first chapters where Rust’s type system stops feeling like syntax and starts feeling like a design tool.
对 Java 开发者来说,这往往是最早能明显体会到“Rust 类型系统已经不只是语法,而是设计工具”的章节之一。
Ownership and Borrowing §§ZH§§ 所有权与借用
Ownership and Borrowing
所有权与借用
What you’ll learn: The core Rust model that replaces GC-managed shared references with explicit ownership, borrowing, and moves.
本章将学习: Rust 最核心的模型,也就是如何用显式的所有权、借用和 move,替代 GC 托管下那种共享引用世界。Difficulty: 🟡 Intermediate
难度: 🟡 中级
This chapter is the real dividing line between “Rust syntax” and “Rust thinking.”
这一章才是真正把“Rust 语法”跟“Rust 思维”分开的分水岭。
Ownership in One Sentence
一句话理解所有权
Every value has an owner, and when that owner goes out of scope, the value is dropped.
每个值都有一个 owner,当 owner 离开作用域时,这个值就会被释放。
Moves
move
#![allow(unused)]
fn main() {
let a = String::from("hello");
let b = a;
// a is no longer usable here
}
For Java developers, this is the first major shock. Assignment is not always “another reference to the same object.” Sometimes it is ownership transfer.
对 Java 开发者来说,这通常是第一次大的冲击。赋值不再总是“多了一个指向同一对象的引用”,它有时真的是所有权转移。
Borrowing
借用
#![allow(unused)]
fn main() {
fn print_name(name: &str) {
println!("{name}");
}
}
Borrowing lets code read a value without taking ownership.
借用允许代码读取一个值,而不用把所有权拿走。
Mutable Borrowing
可变借用
#![allow(unused)]
fn main() {
fn append_world(text: &mut String) {
text.push_str(" world");
}
}
Rust allows mutation through a borrowed path, but only under rules that prevent conflicting access.
Rust 允许通过借用路径修改数据,但前提是必须遵守那套防止冲突访问的规则。
The Important Rule
最关键的规则
At a given moment, you may have:
在同一时刻,只能二选一:
- many immutable references
很多个不可变引用。 - or one mutable reference
或者一个可变引用。
That rule prevents a large class of race conditions and aliasing bugs.
这条规则会拦掉大量竞争条件和别名相关 bug。
Why Java Developers Struggle Here
为什么 Java 开发者常卡在这里
Java normalizes free movement of references. Rust distinguishes very sharply between:
Java 把“引用可以自由移动”这件事默认化了,而 Rust 会非常锋利地区分下面三件事:
- owning a value
拥有一个值。 - borrowing it immutably
不可变借用它。 - borrowing it mutably
可变借用它。
Once that distinction becomes intuitive, the compiler stops feeling hostile.
只要这三种关系开始变得直觉化,编译器就不会再显得像在找茬。
Memory Safety Deep Dive §§ZH§§ 内存安全深入解析
Memory Safety Deep Dive
内存安全深入解析
What you’ll learn: How Rust avoids common memory bugs without a garbage collector and why that changes systems design.
本章将学习: Rust 如何在没有垃圾回收器的前提下避免常见内存错误,以及这为什么会影响系统设计。Difficulty: 🔴 Advanced
难度: 🔴 高级
Rust memory safety is not built on runtime object tracing. It is built on ownership rules, borrow checking, lifetimes, and a carefully limited unsafe escape hatch.
Rust 的内存安全不是靠运行时对象追踪建立起来的,而是靠所有权规则、借用检查、生命周期,以及被严格限制的 unsafe 出口共同撑起来的。
What Rust Tries to Prevent
Rust 要拦哪些问题
- use-after-free
释放后继续使用。 - double free
重复释放。 - data races
数据竞争。 - invalid aliasing
非法别名。 - null dereference in safe code
安全代码里的空指针解引用。
Why This Matters for Java Developers
这对 Java 开发者意味着什么
Java protects against many of these problems through the runtime. Rust shifts more responsibility to compile time, which usually means more work during development and fewer surprises in production.
Java 主要靠运行时兜住这些问题,Rust 则把更多责任前移到编译期。通常这意味着开发时更费脑子,但线上惊喜更少。
Stack and Heap
栈与堆
Rust uses both stack and heap, just like Java ultimately does under the hood. The difference is that value layout and ownership are much more visible in user code.
Rust 当然也同时使用栈和堆,Java 底层也一样。差别在于 Rust 会把值布局和所有权关系更明显地暴露在用户代码里。
Safety as a Design Constraint
把安全当设计约束
In Rust, APIs often become cleaner because ownership must be obvious. That pressure frequently removes ambiguous lifetimes, hidden caches, and casual shared mutation.
在 Rust 里,API 经常会因为“所有权必须明确”而变得更干净。这种压力会顺手清掉很多模糊生命周期、隐藏缓存和随手共享可变状态。
Memory safety in Rust is not a single feature. It is the result of several smaller rules all pushing in the same direction.
Rust 的内存安全不是某个单独大招,而是很多小规则一起朝同一个方向使劲的结果。
Lifetimes Deep Dive §§ZH§§ 生命周期深入解析
Lifetimes Deep Dive
生命周期深入解析
What you’ll learn: What lifetimes actually describe, why they are about relationships rather than durations, and which patterns matter most in real code.
本章将学习: 生命周期真正描述的是什么、为什么它关注的是关系而不是时长,以及真实代码里最重要的几类模式。Difficulty: 🔴 Advanced
难度: 🔴 高级
Lifetimes are often explained badly. They do not mean “how long an object exists in wall-clock time.” They describe how borrowed references relate to one another.
生命周期经常被讲歪。它不是“对象在现实时间里活多久”,而是在描述借用引用之间的关系。
A Small Example
一个小例子
#![allow(unused)]
fn main() {
fn first<'a>(left: &'a str, _right: &'a str) -> &'a str {
left
}
}
The annotation says: the returned reference is tied to the same lifetime relation as the inputs.
这个标注表达的意思是:返回引用和输入引用处在同一组生命周期关系里。
When Lifetimes Show Up
生命周期通常在哪些地方出现
- returning borrowed data
返回借用数据。 - structs that hold references
在结构体里持有引用。 - complex helper functions that connect multiple borrowed values
连接多个借用值的复杂辅助函数。
What Usually Helps
什么做法通常最有帮助
- return owned data when practical
能返回拥有所有权的数据时就优先返回它。 - keep borrow scopes short
尽量把借用作用域压短。 - avoid storing references in structs until necessary
在真的必要之前,先别把引用塞进结构体里。
Many lifetime problems disappear when code ownership becomes clearer.
很多生命周期问题,随着代码所有权关系变清楚,也就自己消失了。
Smart Pointers — Beyond Single Ownership §§ZH§§ 智能指针:超越单一所有权
Smart Pointers: Beyond Single Ownership
智能指针:超越单一所有权
What you’ll learn: When
Box,Rc,Arc,RefCell, andMutexare needed, and how they compare to Java’s always-reference-based object model.
本章将学习: 什么时候该用Box、Rc、Arc、RefCell、Mutex,以及它们和 Java 那种“对象默认全靠引用”模型有什么区别。Difficulty: 🔴 Advanced
难度: 🔴 高级
Java developers are used to object references everywhere. Rust starts from direct ownership and only adds pointer-like wrappers when they are actually needed.
Java 开发者习惯了对象到处都是引用。Rust 则是先从直接所有权开始,只有真的需要时才引入带指针味道的包装类型。
Common Smart Pointers
常见智能指针
| Type | Typical use |
|---|---|
Box<T> | heap allocation with single ownership 单一所有权下的堆分配 |
Rc<T> | shared ownership in single-threaded code 单线程共享所有权 |
Arc<T> | shared ownership across threads 跨线程共享所有权 |
RefCell<T> | checked interior mutability in single-threaded code 单线程内部可变性 |
Mutex<T> | synchronized shared mutable access 同步共享可变访问 |
The Key Difference from Java
和 Java 的关键差异
In Java, shared references are the default. In Rust, shared ownership is a deliberate choice with a specific type.
在 Java 里,共享引用是默认状态;在 Rust 里,共享所有权必须通过专门类型显式表达。
Guidance
使用建议
- use plain values and references first
优先从普通值和引用开始。 - add
Boxwhen recursive or heap-allocated layout is needed
递归结构或需要稳定堆布局时,再加Box。 - add
RcorArconly when multiple owners are truly required
只有真的存在多个 owner 时,再上Rc或Arc。 - pair
ArcwithMutexonly when shared mutable state is unavoidable
只有共享可变状态躲不过去时,才把Arc和Mutex配一起。
These types are powerful, but they are also signals that the ownership model has become more complex.
这些类型很好用,但它们同时也是信号:代码里的所有权关系已经开始变复杂了。
Crates and Modules §§ZH§§ crate 与模块
Crates and Modules
crate 与模块
What you’ll learn: How Rust code organization maps to Java packages, modules, and artifacts.
本章将学习: Rust 的代码组织方式,如何对应 Java 的 package、module 和构建产物。Difficulty: 🟢 Beginner
难度: 🟢 初级
Rust organizes code around crates and modules rather than packages and classpaths.
Rust 围绕 crate 和模块组织代码,而不是围绕 package 和 classpath。
Mental Mapping
心智映射
| Java idea Java 概念 | Rust idea Rust 概念 |
|---|---|
| artifact or module 产物或模块 | crate |
| package | module tree 模块树 |
| package-private or public API 包级或公开 API | module privacy plus pub模块私有性加 pub |
Basic Layout
基本结构
src/
├── main.rs
├── lib.rs
├── api.rs
└── model/
└── user.rs
Visibility
可见性
- items are private by default
默认私有。 pubexposes an item more broadlypub让条目向更外层暴露。pub(crate)exposes within the current cratepub(crate)只在当前 crate 内可见。
This default privacy is stricter than typical Java codebases and often leads to cleaner boundaries.
这种默认私有性通常比常见 Java 代码库更严格,但也经常能逼出更干净的边界。
Guidance
建议
- keep module trees shallow at first
前期模块树不要搞太深。 - design crate boundaries around ownership of concepts, not around arbitrary layering
crate 边界围绕概念归属来设计,不要只按机械分层切。 - expose a small public API and keep the rest internal
公开 API 尽量小,其余内容尽量内部化。
Crates and modules are simpler than many Java build layouts, but they reward deliberate boundary design.
crate 与模块比很多 Java 构建布局都简单,但它很奖励那种边界清楚的设计方式。
Package Management — Cargo vs Maven / Gradle §§ZH§§ 包管理:Cargo 与 Maven / Gradle 对比
Package Management: Cargo vs Maven / Gradle
包管理:Cargo 与 Maven / Gradle
What you’ll learn: How Cargo maps to the build and dependency workflow Java developers know from Maven and Gradle.
本章将学习: Cargo 和 Java 开发者熟悉的 Maven、Gradle 在构建与依赖管理上的对应关系。Difficulty: 🟢 Beginner
难度: 🟢 初级
Cargo is both build tool and package manager. That is the first thing to internalize. In Java, build logic, dependency declarations, testing, packaging, and plugin behavior are often spread across Maven or Gradle configuration plus a pile of conventions. Cargo puts the common path behind one tool with a much smaller surface area.
Cargo 同时承担构建工具和包管理器两个角色,这是最先要建立起来的认知。在 Java 世界里,构建逻辑、依赖声明、测试、打包和插件行为通常散落在 Maven 或 Gradle 配置以及一堆约定里。Cargo 则把常用路径压进了一个更小、更直接的工具表面。
Basic File Mapping
基础文件映射
| Java ecosystem Java 生态 | Rust ecosystem Rust 生态 |
|---|---|
pom.xml or build.gradle.kts | Cargo.toml |
| local Maven cache 本地 Maven 缓存 | Cargo registry cache Cargo 注册表缓存 |
| multi-module build 多模块构建 | workspace |
| plugin goal or task 插件目标或任务 | Cargo subcommand Cargo 子命令 |
| lock file from build tool 构建工具生成的锁文件 | Cargo.lock |
Declaring Dependencies
声明依赖
<!-- Maven -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
# Cargo.toml
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", features = ["json"] }
Cargo dependencies tend to be shorter because the registry is centralized and the package identifier is usually just the crate name.
Cargo 的依赖声明通常更短,因为注册表相对集中,包标识一般也就是 crate 名字本身。
Common Commands
常用命令对照
| Activity 活动 | Maven or Gradle | Cargo |
|---|---|---|
| create project 创建项目 | archetype or init plugin | cargo new app |
| build 构建 | mvn package, gradle build | cargo build |
| run tests 运行测试 | mvn test, gradle test | cargo test |
| run app 运行程序 | plugin task | cargo run |
| add dependency 添加依赖 | edit build file | cargo add crate_name |
| inspect dependency tree 查看依赖树 | mvn dependency:tree, gradle dependencies | cargo tree |
Features vs Profiles and Optional Modules
feature 与 profile、可选模块的差异
Cargo features are compile-time switches attached to a crate.
Cargo feature 是绑定在 crate 上的编译期开关。
[features]
default = ["json"]
json = []
postgres = ["dep:sqlx"]
[dependencies]
sqlx = { version = "0.8", optional = true }
This is closer to optional modules plus conditional compilation than to a typical Maven profile. Features change the code that is compiled, not just the command that runs.
它更接近“可选模块 + 条件编译”,而不是传统 Maven profile。feature 改变的是参与编译的代码,而不只是执行哪条命令。
Workspaces vs Multi-Module Builds
workspace 与多模块构建
[workspace]
members = ["api", "core", "cli"]
resolver = "2"
A Cargo workspace looks familiar to anyone who has worked in a multi-module Java repository. The difference is that the defaults are simpler: shared lock file, shared target directory, and consistent commands from the repository root.
只要做过多模块 Java 仓库,这个结构就不会陌生。差别在于 Cargo 的默认行为更简单:共享锁文件、共享 target 目录,并且可以从仓库根目录统一执行命令。
Practical Advice for Java Developers
给 Java 开发者的实际建议
- Start by learning
cargo build,cargo test,cargo run,cargo fmt, andcargo clippy.
先把cargo build、cargo test、cargo run、cargo fmt、cargo clippy用熟。 - Treat
Cargo.tomlas source code rather than XML ceremony.
把Cargo.toml当成源代码的一部分,而不是额外的 XML 仪式。 - Prefer a small number of well-understood crates instead of recreating the “plugin zoo” habit.
优先使用少量真正理解清楚的 crate,别把“插件动物园”习惯带过来。 - Read feature flags carefully before adding dependencies to production services.
在生产服务里引入依赖前,先把 feature 开关看明白。
After a few days, Cargo stops feeling exotic and starts feeling like the build tool Java developers always wanted to have.
熟悉几天之后,Cargo 往往就不再显得陌生,反而会像是 Java 开发者一直想要但没真正拥有过的那种构建工具。
Error Handling §§ZH§§ 错误处理
Error Handling
错误处理
What you’ll learn: How
Resultchanges API design, how Rust error propagation compares to Java exceptions, and when to use domain-specific error types.
本章将学习:Result会怎样改变 API 设计、Rust 的错误传播和 Java 异常机制有什么根本区别,以及什么时候该用领域错误类型。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Rust pushes errors into the type system. That changes design decisions much earlier than Java developers are used to.
Rust 会把错误直接推进类型系统里,这会比 Java 开发者习惯的时机更早地影响 API 设计。
Exceptions vs Result
异常与 Result
User loadUser(long id) throws IOException {
// caller must read documentation or signatures carefully
}
#![allow(unused)]
fn main() {
fn load_user(id: u64) -> Result<User, LoadUserError> {
// the error type is part of the return value
}
}
In Java, exceptions separate the main return type from the failure path. In Rust, success and failure sit next to each other in the function signature.
在 Java 里,异常把主返回值和失败路径拆开了;在 Rust 里,成功和失败会并排体现在函数签名里。
The ? Operator
? 运算符
#![allow(unused)]
fn main() {
fn load_config(path: &str) -> Result<String, std::io::Error> {
let text = std::fs::read_to_string(path)?;
Ok(text)
}
}
? is the standard way to propagate an error upward without writing repetitive match blocks everywhere.? 是向上传播错误的标准写法,可以省掉一堆重复的 match 样板。
Domain Error Enums
领域错误枚举
#![allow(unused)]
fn main() {
#[derive(Debug, thiserror::Error)]
enum LoadUserError {
#[error("database error: {0}")]
Database(String),
#[error("user {0} not found")]
NotFound(u64),
}
}
For Java developers, this often replaces a hierarchy of custom exceptions with one explicit sum type.
对 Java 开发者来说,这通常意味着:原来那种自定义异常层级,会被一个显式的错误和类型替掉。
Option vs Result
Option 与 Result
Use Option<T> when absence is normal. Use Result<T, E> when failure carries explanation or needs handling.
“值不存在”本来就是正常情况时,用 Option<T>;失败需要解释、需要处理时,用 Result<T, E>。
Practical Advice
实战建议
- Avoid
unwrap()in real application paths.
真实应用路径里尽量别滥用unwrap()。 - Start with simple error enums before reaching for generalized error wrappers.
先从简单错误枚举起步,再考虑通用错误包装。 - Let library APIs be precise; let application entry points convert errors into user-facing output.
库层 API 要尽量精确,应用入口再把错误翻译成用户能看懂的输出。
Rust error handling feels strict at first, but that strictness removes a huge amount of hidden control flow.
Rust 的错误处理一开始会显得严,但这种“严”会帮忙清掉大量隐藏控制流。
Crate-Level Error Types and Result Aliases §§ZH§§ crate 级错误类型与 Result 别名
Crate-Level Error Types and Result Aliases
crate 级错误类型与 Result 别名
What you’ll learn: How Java exception habits map to Rust crate-level error enums, how
AppErrorplusAppResult<T>keeps code readable, and how this pattern replaces scattered exception classes in Rust services.
本章将学习: Java 的异常习惯如何迁移到 Rust 的 crate 级错误枚举,AppError加AppResult<T>为什么能让代码更整洁,以及这套模式如何替代分散的异常类体系。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Java developers are used to methods that may throw many different exceptions without showing the full failure contract in the signature.
Java 开发者很习惯“方法可能抛出很多异常,但签名里并不完整展示失败契约”这种做法。
Rust prefers a different style: define one central error enum for the crate and return it explicitly.
Rust 更偏好另一种风格:为整个 crate 定义一个中心错误枚举,然后显式返回它。
The Core Pattern
核心模式
#![allow(unused)]
fn main() {
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Serialization error: {0}")]
Json(#[from] serde_json::Error),
#[error("Validation error: {message}")]
Validation { message: String },
#[error("Not found: {entity} with id {id}")]
NotFound { entity: String, id: String },
}
pub type AppResult<T> = std::result::Result<T, AppError>;
}
The alias is not decorative.
这个别名不是装饰品。
It turns every signature into a house style that stays readable across repository, service, and handler layers.
它会把 repository、service、handler 各层的函数签名统一成一种易读的团队风格。
#![allow(unused)]
fn main() {
pub async fn get_user(id: Uuid) -> AppResult<User> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_optional(&pool)
.await?;
user.ok_or_else(|| AppError::NotFound {
entity: "user".into(),
id: id.to_string(),
})
}
}
Why This Feels Different from Java
这和 Java 的体感为什么不一样
In Java, a method can look simple because the exception contract is partly hidden in runtime behavior.
在 Java 里,一个方法之所以看起来简洁,往往是因为异常契约有一部分被藏进了运行时行为里。
In Rust, failure is part of the function type.
在 Rust 里,失败本身就是函数类型的一部分。
That means callers do not guess; they handle a typed result.
这意味着调用方不是靠猜,而是在处理一个有类型约束的结果。
Replacing Exception Forests
替代一大片异常类森林
Java codebases often end up with many parallel exception classes:
很多 Java 项目最后都会长出一大片平行存在的异常类:
ValidationException
校验异常。UserNotFoundException
用户不存在异常。RepositoryException
仓储异常。RemoteServiceException
远程服务异常。
Rust can pull the same vocabulary into one enum and make the set visible in one place.
Rust 可以把这些失败语义拉回一个枚举里,并且让整个失败集合集中可见。
#![allow(unused)]
fn main() {
#[derive(thiserror::Error, Debug)]
pub enum UserServiceError {
#[error("validation failed: {0}")]
Validation(String),
#[error("user {0} not found")]
UserNotFound(String),
#[error("email already exists: {0}")]
DuplicateEmail(String),
#[error(transparent)]
Database(#[from] sqlx::Error),
}
}
Rust’s Answer to @ControllerAdvice
Rust 对 @ControllerAdvice 的回答
Spring Boot usually converts exceptions to HTTP responses in a centralized place.
Spring Boot 往往会在一个集中的位置把异常翻译成 HTTP 响应。
Rust web frameworks usually express that with IntoResponse.
Rust Web 框架通常会用 IntoResponse 来表达同样的职责。
#![allow(unused)]
fn main() {
impl IntoResponse for AppError {
fn into_response(self) -> Response {
match self {
AppError::Validation { message } => (
StatusCode::BAD_REQUEST,
Json(ErrorBody {
code: "validation_error",
message,
}),
)
.into_response(),
AppError::NotFound { entity, id } => (
StatusCode::NOT_FOUND,
Json(ErrorBody {
code: "not_found",
message: format!("{entity} {id} was not found"),
}),
)
.into_response(),
AppError::Database(error) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorBody {
code: "database_error",
message: error.to_string(),
}),
)
.into_response(),
AppError::Json(error) => (
StatusCode::BAD_REQUEST,
Json(ErrorBody {
code: "invalid_json",
message: error.to_string(),
}),
)
.into_response(),
}
}
}
}
The architectural role is similar, but the implementation is driven by plain types instead of reflection and advice rules.
两者在架构职责上很像,但 Rust 这边是由普通类型驱动,而不是靠反射和 advice 规则完成。
Why #[from] Matters
为什么 #[from] 很关键
#[from] lets infrastructure errors flow upward without repetitive wrapping code.#[from] 可以让基础设施层错误向上传播时不必每次都手工包一层。
#![allow(unused)]
fn main() {
#[derive(thiserror::Error, Debug)]
pub enum ImportError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("CSV parse error: {0}")]
Csv(#[from] csv::Error),
#[error("row {row}: invalid email")]
InvalidEmail { row: usize },
}
}
This is the Rust alternative to a long chain of catch, wrap, and rethrow blocks.
这就是 Rust 对“层层 catch、包装、再抛出”的替代方案。
thiserror vs anyhow
thiserror 和 anyhow 的分工
| Tool 工具 | Best use 最佳用途 |
|---|---|
thiserror | library crates, service layers, reusable modules 库 crate、服务层、可复用模块 |
anyhow | binaries, CLI entrypoints, one-off tools 可执行程序、CLI 入口、一次性工具 |
A good house rule for Java-to-Rust migration is simple:
面向 Java 迁移时,一个很好用的团队规则很简单:
- reusable layers use
thiserror
可复用层用thiserror。 - outer executable boundaries use
anyhow
最外层可执行边界用anyhow。
Practical Rules
实用规则
- keep one central error enum per crate whenever possible
可以的话,每个 crate 保持一个中心错误枚举。 - use variants for domain failures that callers may distinguish
调用方需要区分的领域失败,用独立变体表示。 - use
#[from]for infrastructure failures that should bubble up
需要向上传播的基础设施失败,用#[from]承接。 - convert to HTTP or CLI output only at the outer boundary
只有在最外层边界才去翻译成 HTTP 响应或 CLI 输出。
Traits and Generics §§ZH§§ Trait 与泛型
Traits and Generics
Trait 与泛型
What you’ll learn: How Rust traits compare to Java interfaces and how Rust generics differ from erased JVM generics.
本章将学习: Rust trait 如何对应 Java interface,以及 Rust 泛型和 JVM 擦除式泛型有什么根本区别。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Traits are the closest Rust concept to Java interfaces, but they sit inside a more powerful type system.
trait 是 Rust 里最接近 Java interface 的概念,但它所在的类型系统更强,也更直接。
Traits vs Interfaces
trait 与接口
#![allow(unused)]
fn main() {
trait Render {
fn render(&self) -> String;
}
}
Traits can define required behavior and default behavior, much like interfaces with default methods.
trait 可以定义必须实现的行为,也可以带默认实现,这点和带 default method 的接口很像。
Generics
泛型
#![allow(unused)]
fn main() {
fn first<T>(items: &[T]) -> Option<&T> {
items.first()
}
}
Rust generics are monomorphized in many cases, which means the compiler often generates concrete machine code per concrete type rather than relying on erased runtime dispatch.
Rust 泛型在很多情况下会做单态化,也就是说编译器会针对具体类型生成具体机器码,而不是像 JVM 那样主要依赖擦除后的运行时分发。
Static vs Dynamic Dispatch
静态分发与动态分发
- generic trait bounds usually mean static dispatch
泛型 trait bound 通常意味着静态分发。 dyn Traitmeans dynamic dispatchdyn Trait则表示动态分发。
This distinction is far more explicit than in typical Java code.
这种区别在 Rust 里写得非常明白,比典型 Java 代码显式得多。
Why Java Developers Should Care
Java 开发者为什么要在意这件事
Java interfaces often coexist with inheritance, reflection, and proxies. Rust traits tend to stay closer to behavior and less tied to framework machinery.
Java interface 经常和继承、反射、代理一起工作;Rust trait 则更接近纯行为抽象,也更少和框架魔法绑死。
Traits and generics are where Rust starts feeling less like “Java without GC” and more like its own language with its own power.
trait 和泛型这块,往往是 Rust 最开始不再像“去掉 GC 的 Java”,而真正显出自己语言气质的地方。
Generic Constraints §§ZH§§ 泛型约束
Generic Constraints
泛型约束
What you’ll learn: How trait bounds and
whereclauses compare to Java generic bounds.
本章将学习: trait bound 和where子句分别怎样对应 Java 的泛型约束。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Java developers know bounds such as <T extends Comparable<T>>. Rust expresses similar ideas through trait bounds.
Java 开发者很熟悉 <T extends Comparable<T>> 这种写法。Rust 则通过 trait bound 表达类似意思。
#![allow(unused)]
fn main() {
fn sort_and_print<T: Ord + std::fmt::Debug>(items: &mut [T]) {
items.sort();
println!("{items:?}");
}
}
The same bounds can be moved into a where clause for readability:
如果约束太长,也可以挪进 where 子句提升可读性:
#![allow(unused)]
fn main() {
fn sort_and_print<T>(items: &mut [T])
where
T: Ord + std::fmt::Debug,
{
items.sort();
println!("{items:?}");
}
}
Key Difference from Java
和 Java 的关键差异
Rust bounds are closely tied to behavior required by the compiler and standard library traits. They are not just nominal inheritance constraints.
Rust 里的约束更紧密地绑定在行为能力上,通常是编译器和标准库 trait 真正需要的能力,而不只是名义上的继承关系。
Advice
建议
- use inline bounds for short signatures
签名短时直接写内联约束。 - use
whereclauses when bounds become long
约束一长,就改用where。 - think in capabilities, not class hierarchies
思考方式优先放在“能力”,不是类层级。
Inheritance vs Composition §§ZH§§ 继承与组合
Inheritance vs Composition
继承与组合
What you’ll learn: Why Rust favors composition over class inheritance and how Java design patterns change under that pressure.
本章将学习: 为什么 Rust 明显偏向组合而不是类继承,以及 Java 设计模式在这种压力下会怎么变形。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Rust has no class inheritance. That is not a missing feature by accident; it is a design decision.
Rust 没有类继承,这不是漏掉了什么,而是刻意的设计选择。
What Replaces Inheritance
什么东西替代了继承
- traits for shared behavior
trait 承载共享行为。 - structs for data ownership
struct 承载数据所有权。 - delegation for reuse
委托负责复用。 - enums for explicit variant modeling
enum 负责显式的分支建模。
Why This Helps
这样做有什么好处
Inheritance-heavy code often mixes state sharing, behavioral polymorphism, and framework convenience into one mechanism. Rust separates those concerns, which can make designs flatter and easier to audit.
重继承代码经常把状态共享、行为多态和框架便利性揉进一个机制里。Rust 则会把这些关注点拆开,所以设计通常更扁平,也更容易审计。
Advice for Java Developers
给 Java 开发者的建议
- model behavior with traits
行为抽象优先用 trait。 - reuse implementation through helper types and delegation
实现复用优先靠辅助类型和委托。 - use enums where inheritance trees only exist to model variants
如果继承树只是为了表示“几种变体”,那大概率该换成 enum。
Composition in Rust is usually less magical and more honest about where behavior really lives.
Rust 里的组合通常更少魔法,也更诚实地表达“行为到底放在哪”。
Object-Oriented Thinking in Rust §§ZH§§ Rust 中的面向对象思维
Object-Oriented Thinking in Rust
Rust 中的面向对象思维
What you’ll learn: How Java’s object-oriented instincts map into Rust, what Rust keeps from classic OOP, what it rejects, and how to redesign Java service and domain models without forcing Rust into a class hierarchy.
本章将学习: Java 的面向对象直觉如何迁移到 Rust,Rust 保留了经典 OOP 的哪些部分、明确舍弃了哪些部分,以及怎样重构 Java 的服务与领域模型,而不是强行把 Rust 写成 class 层级。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Java developers usually carry four strong OOP instincts:
Java 开发者通常会带着四种很强的面向对象直觉:
- bundle data and behavior together
把数据和行为绑在一起。 - reuse through inheritance
通过继承做复用。 - hide implementation behind interfaces
通过接口隐藏实现。 - let frameworks create and wire object graphs
让框架负责创建和装配对象图。
Rust认可其中一部分,但也会明确拒绝另一部分。
Rust agrees with some of that package, and clearly rejects the rest.
What Rust Keeps
Rust 保留了什么
- encapsulation
封装。 - methods on user-defined types
自定义类型上的方法。 - interface-like abstraction through traits
通过 trait 实现接口式抽象。 - polymorphism through generics and trait objects
通过泛型和 trait object 实现多态。
So the right takeaway is not “Rust has no OOP.”
因此,正确的理解绝不是“Rust 没有 OOP”。
The right takeaway is that Rust keeps the useful parts of OOP and drops the class-centric worldview.
更准确的理解是:Rust 保留了 OOP 里真正有用的部分,同时丢掉了以 class 为中心的世界观。
What Rust Rejects
Rust 拒绝什么
- class inheritance as the main reuse mechanism
把类继承当成主要复用机制。 - “everything is an object” as the default worldview
把“万物皆对象”当成默认世界观。 - hidden ownership behind ambient references
把所有权隐藏在到处可拿的引用后面。 - framework-controlled object graphs as the normal structure source
把框架控制的对象图当成结构的默认来源。
This is why Java-shaped Rust often feels awkward.
这也是为什么很多“Java 味道过重”的 Rust 代码会显得很别扭。
A Practical Translation Table
一张实用映射表
| Java OOP habit Java OOP 习惯 | Better Rust direction 更合适的 Rust 方向 |
|---|---|
| entity class | struct |
| service interface | trait |
| abstract base class | trait plus helper struct or enumtrait 配合辅助 struct 或 enum |
| field injection | explicit constructor wiring 显式构造装配 |
| inheritance reuse | composition and delegation 组合与委托 |
| nullable property | Option<T> |
| exception flow | Result<T, E> |
Encapsulation Still Exists
封装依然存在
#![allow(unused)]
fn main() {
pub struct Counter {
value: u64,
}
impl Counter {
pub fn new() -> Self {
Self { value: 0 }
}
pub fn increment(&mut self) {
self.value += 1;
}
pub fn value(&self) -> u64 {
self.value
}
}
}
Data and methods still live together, but inheritance is no longer the glue.
数据和方法依然可以放在一起,只不过把它们粘起来的东西不再是继承层级。
Traits Are Interface-Like, Not Class-Like
Trait 更像接口,不像类
#![allow(unused)]
fn main() {
trait PaymentGateway {
fn charge(&self, cents: u64) -> Result<(), String>;
}
struct StripeGateway;
impl PaymentGateway for StripeGateway {
fn charge(&self, cents: u64) -> Result<(), String> {
println!("charging {cents}");
Ok(())
}
}
}
Traits give Java developers familiar abstraction power, but Rust does not expect a base class to sit underneath everything.
trait 会提供 Java 开发者熟悉的抽象能力,但 Rust 并不期待所有东西下面都躺着一个基类。
Polymorphism Without Inheritance
没有继承也能实现多态
Static dispatch with generics
用泛型做静态分发
#![allow(unused)]
fn main() {
fn checkout<G: PaymentGateway>(gateway: &G, cents: u64) -> Result<(), String> {
gateway.charge(cents)
}
}
Dynamic dispatch with trait objects
用 trait object 做动态分发
#![allow(unused)]
fn main() {
fn checkout_dyn(gateway: &dyn PaymentGateway, cents: u64) -> Result<(), String> {
gateway.charge(cents)
}
}
For Java developers, the crucial shift is that dispatch choice is explicit instead of being silently bundled with class inheritance.
对 Java 开发者来说,最关键的变化在于:分发方式是显式选择的,而不是默认绑死在类继承上。
Composition Beats Inheritance
组合通常胜过继承
abstract class BaseService {
protected final AuditClient auditClient;
protected BaseService(AuditClient auditClient) {
this.auditClient = auditClient;
}
}
#![allow(unused)]
fn main() {
struct AuditClient;
impl AuditClient {
fn send(&self, message: &str) {
println!("audit: {message}");
}
}
struct UserService {
audit: AuditClient,
}
impl UserService {
fn create_user(&self, email: &str) {
self.audit.send(&format!("create user {email}"));
}
}
}
Shared capability comes from shared fields and delegation, not from a parent class chain.
能力复用来自共享字段和委托,而不是来自一条父类链。
Closed Variation Often Wants enum
封闭变体通常更适合 enum
#![allow(unused)]
fn main() {
enum Notification {
Email { address: String },
Sms { number: String },
Push { device_id: String },
}
fn send(notification: Notification) {
match notification {
Notification::Email { address } => println!("email {address}"),
Notification::Sms { number } => println!("sms {number}"),
Notification::Push { device_id } => println!("push {device_id}"),
}
}
}
If the set of cases is already known, an enum is often more honest than an abstract hierarchy.
如果所有情况本来就已经知道了,那 enum 往往比抽象层级更诚实。
Service Design Without a DI Container
没有 DI 容器时如何设计服务
#![allow(unused)]
fn main() {
struct UserRepository;
struct EmailClient;
struct UserService {
repo: UserRepository,
email: EmailClient,
}
impl UserService {
fn new(repo: UserRepository, email: EmailClient) -> Self {
Self { repo, email }
}
}
}
This looks more manual at first, but it becomes much easier to read and debug because the dependency graph is plain code.
这乍看会更“手工”,但由于依赖图就是普通代码,阅读和调试都会轻松很多。
Better Questions for Java Developers
更适合 Java 开发者的新问题
Instead of asking:
别再优先问这些:
- what is the base class?
基类是什么? - where is the DI container?
DI 容器在哪? - which abstract service owns this behavior?
哪一个抽象服务拥有这段行为?
Ask:
更应该问这些:
- who owns this data?
这份数据归谁拥有? - is this variation open or closed?
这类变化是开放的还是封闭的? - does this behavior need static or dynamic dispatch?
这段行为需要静态分发还是动态分发? - should this be a trait, a struct, or an enum?
这里到底该用 trait、struct,还是 enum?
Once these questions change, Rust stops feeling restrictive and starts opening design space.
一旦提问方式变了,Rust 就不再像一门处处设限的语言,而会开始真正打开设计空间。
From and Into Traits §§ZH§§ From 与 Into Trait
Type Conversions in Rust
Rust 中的类型转换
What you’ll learn: How Rust conversion traits map to Java constructors, static factories, DTO mappers, and parsing APIs, plus when to use
From,Into,TryFrom, andFromStrin real service code.
本章将学习: Rust 的转换 trait 如何对应 Java 的构造器、静态工厂、DTO 映射器与解析 API,以及在真实服务代码里什么时候该用From、Into、TryFrom、FromStr。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Java projects often express conversions through constructors, of(...), valueOf(...), or mapper classes.
Java 项目通常会用构造器、of(...)、valueOf(...),或者 mapper 类来表达各种转换。
Rust pulls that intent into a small set of standard traits.
Rust 会把这些意图收束到一小组标准 trait 里。
The Core Distinction
核心区别
From<T>means the conversion cannot failFrom<T>表示转换一定成功。TryFrom<T>means validation is requiredTryFrom<T>表示转换前必须校验。FromStrmeans parse text into a valueFromStr表示把文本解析成值。Into<T>is what callers usually use afterFrom<T>existsInto<T>往往是调用方在From<T>存在之后更方便使用的入口。
Java Mapper vs Rust Trait Impl
Java Mapper 和 Rust Trait 实现的对照
public record UserDto(String id, String email) { }
public final class User {
private final UUID id;
private final String email;
private User(UUID id, String email) {
this.id = id;
this.email = email;
}
public static User fromDto(UserDto dto) {
return new User(UUID.fromString(dto.id()), dto.email());
}
}
#![allow(unused)]
fn main() {
#[derive(Debug)]
struct UserDto {
id: String,
email: String,
}
#[derive(Debug)]
struct User {
id: uuid::Uuid,
email: String,
}
impl TryFrom<UserDto> for User {
type Error = String;
fn try_from(dto: UserDto) -> Result<Self, Self::Error> {
let id = dto.id.parse().map_err(|_| "invalid UUID".to_string())?;
if !dto.email.contains('@') {
return Err("invalid email".into());
}
Ok(User {
id,
email: dto.email,
})
}
}
}
The business purpose is the same, but Rust turns the conversion rule into part of the type system.
业务目的其实是一样的,但 Rust 会把这条转换规则直接写进类型系统里。
From for Infallible Conversions
用 From 表示必然成功的转换
#![allow(unused)]
fn main() {
#[derive(Debug)]
struct UserId(uuid::Uuid);
impl From<uuid::Uuid> for UserId {
fn from(value: uuid::Uuid) -> Self {
Self(value)
}
}
impl From<UserId> for uuid::Uuid {
fn from(value: UserId) -> Self {
value.0
}
}
}
If no validation is needed, From is the most direct expression.
如果完全不需要校验,From 就是最直接的表达方式。
Into for Convenient Call Sites
用 Into 让调用点更顺手
#![allow(unused)]
fn main() {
fn load_user(id: impl Into<UserId>) {
let id = id.into();
println!("loading {:?}", id);
}
let uuid = uuid::Uuid::new_v4();
load_user(UserId::from(uuid));
}
APIs often accept Into<T> so callers can pass either the wrapped type or something easy to wrap.
很多 API 会选择接收 Into<T>,这样调用方既可以传已经包装好的值,也可以传容易包装进去的值。
TryFrom for DTO-to-Domain Boundaries
DTO 到领域对象边界上用 TryFrom
This is one of the most useful patterns for Java backend teams.
这其实是 Java 后端团队最该尽快掌握的模式之一。
#![allow(unused)]
fn main() {
#[derive(Debug)]
struct CreateUserRequest {
email: String,
age: u8,
}
#[derive(Debug)]
struct NewUser {
email: String,
age: u8,
}
impl TryFrom<CreateUserRequest> for NewUser {
type Error = String;
fn try_from(value: CreateUserRequest) -> Result<Self, Self::Error> {
if !value.email.contains('@') {
return Err("email must contain @".into());
}
if value.age < 18 {
return Err("user must be an adult".into());
}
Ok(Self {
email: value.email.trim().to_lowercase(),
age: value.age,
})
}
}
}
This is the Rust version of validating request data before creating a stronger domain object.
这就是 Rust 版本的“先验证请求数据,再创建更强约束的领域对象”。
FromStr for Parsing
用 FromStr 表示解析
#![allow(unused)]
fn main() {
use std::str::FromStr;
#[derive(Debug, Clone, Copy)]
enum Environment {
Local,
Staging,
Production,
}
impl FromStr for Environment {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"local" => Ok(Self::Local),
"staging" => Ok(Self::Staging),
"production" => Ok(Self::Production),
other => Err(format!("unknown environment: {other}")),
}
}
}
}
This is the natural Rust equivalent of UUID.fromString, Integer.parseInt, or Spring binder conversion.
这就是 Rust 对 UUID.fromString、Integer.parseInt、Spring 绑定器转换的自然对应物。
Display for Rendering
用 Display 负责展示
#![allow(unused)]
fn main() {
use std::fmt;
struct AccountNumber(String);
impl fmt::Display for AccountNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "acct:{}", self.0)
}
}
}
Parsing and rendering stay separate in Rust, which usually makes formatting code easier to reason about.
Rust 会把“解析”和“展示”分开处理,这通常会让格式化代码更清晰。
Quick Rules
快速规则
| Java habit Java 习惯 | Better Rust choice 更合适的 Rust 选择 |
|---|---|
| constructor that always succeeds 一定成功的构造器 | From<T> |
| static factory that may reject input 可能拒绝输入的静态工厂 | TryFrom<T> |
valueOf(String) or parservalueOf(String) 或解析器 | FromStr |
| standalone mapper service everywhere 到处都是独立 mapper 服务 | put trait impls near the types 把 trait 实现贴近类型本身 |
Common Mistakes
常见误区
- using
From<T>for a conversion that can fail
明明可能失败,却硬写成From<T>。 - spreading validation across controllers and services
把校验逻辑拆散到 controller 和 service 各处。 - passing raw
Stringeverywhere instead of introducing value objects
到处乱传裸String,而不是建立小型值对象。
Closures and Iterators §§ZH§§ 闭包与迭代器
Closures and Iterators
闭包与迭代器
What you’ll learn: How Rust closures compare to Java lambdas and how iterators relate to the Stream API.
本章将学习: Rust 闭包如何对应 Java lambda,以及迭代器如何对应 Stream API。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Closures feel familiar to Java developers because lambdas are already common. The difference is in capture behavior and ownership.
闭包对 Java 开发者来说不算陌生,因为 lambda 早就很常见了。真正的差异主要落在捕获行为和所有权上。
Closures
闭包
#![allow(unused)]
fn main() {
let factor = 2;
let multiply = |x: i32| x * factor;
}
Rust closures can capture by borrow or by move. That makes them more explicit in ownership-sensitive contexts such as threads and async tasks.
Rust 闭包既可以按借用捕获,也可以按 move 捕获,所以在线程、异步任务这类所有权敏感场景里会更明确。
Fn, FnMut, FnOnce
Fn、FnMut、FnOnce
These traits describe how a closure interacts with captured state:
这三个 trait 描述的是闭包和捕获状态的关系:
Fn: immutable capture
不可变捕获。FnMut: mutable capture
可变捕获。FnOnce: consumes captured values
消耗捕获值。
This is a deeper model than Java lambdas usually expose.
这一层模型比 Java lambda 平时显露出来的语义更深。
Iterators vs Streams
迭代器与 Stream
Both are lazy pipelines. Rust iterators tend to compose with less framework overhead and with stronger compile-time specialization.
两者都是惰性流水线,不过 Rust 迭代器通常框架负担更小,编译期特化更强。
#![allow(unused)]
fn main() {
let result: Vec<_> = values
.iter()
.filter(|x| **x > 10)
.map(|x| x * 2)
.collect();
}
Advice
建议
- closures are easy; closure capture semantics are the real lesson
闭包本身不难,真正要学的是捕获语义。 - iterator chains are normal Rust, not niche functional style
迭代器链是 Rust 日常写法,不是什么小众函数式花活。 - if ownership errors appear in iterator code, inspect whether the chain borrows or consumes values
迭代器代码里一旦冒出所有权报错,先查这条链到底是在借用还是在消耗值。
Macros Primer §§ZH§§ 宏入门
Macros Primer
宏入门
What you’ll learn: Why Rust macros exist, how they differ from Java annotations or code generation, and which macros matter first.
本章将学习: Rust 宏为什么存在、它和 Java 注解或代码生成有什么差别,以及最先应该认识哪些宏。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Macros in Rust are syntax-level generation tools. They are much closer to language extension points than to Java annotations.
Rust 宏是语法级生成工具,它更接近语言扩展点,而不是 Java 注解那一路。
First Macros to Recognize
最先要认熟的宏
println!vec!format!dbg!#[derive(...)]
Why Java Developers Should Care
Java 开发者为什么要关心
In Java, many conveniences come from frameworks, annotation processors, Lombok-style generation, or reflection. Rust often solves the same ergonomics problem earlier in the compilation pipeline through macros.
Java 里很多便利性来自框架、注解处理器、Lombok 式代码生成或者反射。Rust 则经常在更前面的编译阶段通过宏把这件事解决掉。
Practical Advice
实用建议
- learn to read macro invocations before learning to write macros
先学会读宏,再学写宏。 - treat derive macros as the normal entry point
把 derive 宏当成最自然的入口。 - use
cargo expandwhen a macro stops making sense
一旦宏看不懂,就用cargo expand。
Macros are powerful, but most day-to-day Rust work only needs comfort with using them, not authoring them.
宏确实很强,但绝大多数日常 Rust 开发只需要会用,不需要一上来就自己写。
Concurrency §§ZH§§ 并发
Concurrency
并发
What you’ll learn: How Rust concurrency compares to Java threads, executors, and synchronized shared state.
本章将学习: Rust 并发模型如何对应 Java 线程、执行器以及同步共享状态。Difficulty: 🔴 Advanced
难度: 🔴 高级
Java gives teams mature concurrency tools. Rust brings a different advantage: the compiler participates more directly in preventing misuse.
Java 已经给团队提供了非常成熟的并发工具,而 Rust 的优势则落在另一边:编译器会更直接地参与阻止误用。
Core Mapping
核心映射
| Java | Rust |
|---|---|
Thread | std::thread::spawn |
ExecutorService | async runtime or manual thread orchestration 异步运行时或手工线程编排 |
| synchronized mutable state 同步共享可变状态 | Mutex<T> |
| concurrent shared ownership 并发共享所有权 | Arc<T> |
| queues and handoff 队列与交接 | channels |
Shared State
共享状态
#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
}
Rust makes the ownership and synchronization cost explicit in the type spelling.
Rust 会把所有权和同步成本直接写在类型上,不让它们偷偷躲起来。
Send and Sync
Send 与 Sync
These marker traits are part of what makes Rust concurrency feel stricter:
Rust 并发之所以显得更严,Send 和 Sync 是关键原因之一:
Send: a value can move across threads
值可以跨线程移动。Sync: references to a value can be shared across threads safely
值的引用可以安全地跨线程共享。
Java developers rarely think at this level because the JVM and library conventions hide it.
Java 开发者很少在这个层面思考问题,因为 JVM 和库约定通常把这层细节藏起来了。
Advice
建议
- prefer message passing when shared mutable state is not necessary
只要能避免共享可变状态,就优先消息传递。 - when shared state is necessary, make the synchronization type explicit
共享状态躲不过去时,就把同步类型老老实实写出来。 - let the compiler teach where thread-safety assumptions break
让编译器来指出线程安全假设在哪些地方站不住。
Rust does not make concurrency easy by hiding the problem. It makes it safer by forcing the important parts into the type system.
Rust 不是靠把并发问题藏起来来显得简单,而是靠把关键约束推进类型系统里来变得更安全。
Async/Await Deep Dive §§ZH§§ Async/Await 深入解析
Async Programming: CompletableFuture vs Rust Future
异步编程:CompletableFuture 与 Rust Future
What you’ll learn: The runtime model behind Rust async, how it differs from Java’s eager futures, and which patterns correspond to
CompletableFuture, executors, and timeouts.
本章将学习: Rust async 背后的运行时模型、它和 Java eager future 的区别,以及哪些模式分别对应CompletableFuture、执行器和超时控制。Difficulty: 🔴 Advanced
难度: 🔴 高级
Rust and Java both talk about futures, but the execution model is not the same.
Rust 和 Java 都会讲 future,但两边的执行模型并不是一回事。
The First Big Difference: Rust Futures Are Lazy
第一个大差异:Rust future 是惰性的
CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> fetchFromRemote());
That Java future starts work as soon as it is scheduled on an executor.
这类 Java future 一旦被执行器接管,任务就开始推进了。
#![allow(unused)]
fn main() {
async fn fetch_from_remote() -> String {
"done".to_string()
}
let future = fetch_from_remote();
// nothing happens yet
let value = future.await;
}
In Rust, creating the future does not start execution. Polling by an executor starts progress.
在 Rust 里,光把 future 创建出来并不会自动执行。只有被执行器轮询之后,任务才会真正推进。
Why Tokio Exists
为什么 Rust 里会有 Tokio
Java ships with threads, executors, and a rich runtime by default. Rust does not include a default async runtime in the language. That is why libraries such as Tokio exist.
Java 默认就带着线程、执行器和比较完整的运行时能力。Rust 语言本身没有默认 async 运行时,所以才会有 Tokio 这种库承担调度器、定时器和 IO 驱动的职责。
#[tokio::main]
async fn main() {
let body = reqwest::get("https://example.com")
.await
.unwrap()
.text()
.await
.unwrap();
println!("{body}");
}
The runtime owns the scheduler, timers, IO drivers, and task system.
运行时负责调度器、定时器、IO 驱动和任务系统。
Mental Mapping
心智映射表
| Java | Rust |
|---|---|
CompletableFuture<T> | Future<Output = T> |
ExecutorService | Tokio runtime or another async executor Tokio 运行时或其他异步执行器 |
CompletableFuture.allOf(...) | join! or try_join! |
orTimeout(...) | tokio::time::timeout(...) |
| cancellation 取消 | dropping the future or explicit cancellation primitives 丢弃 future 或使用显式取消原语 |
Concurrency Pattern: Wait for Many Tasks
并发模式:等待多个任务
var userFuture = client.fetchUser(id);
var ordersFuture = client.fetchOrders(id);
var result = userFuture.thenCombine(ordersFuture, Combined::new);
#![allow(unused)]
fn main() {
let user = fetch_user(id);
let orders = fetch_orders(id);
let (user, orders) = tokio::join!(user, orders);
}
Rust keeps the control flow flatter. The combined result is often easier to read because .await and join! look like normal program structure instead of chained callbacks.
Rust 通常会让控制流更平。.await 和 join! 看起来更像普通程序结构,而不是一串层层套下去的回调拼接。
Timeouts and Cancellation
超时与取消
#![allow(unused)]
fn main() {
use std::time::Duration;
let result = tokio::time::timeout(Duration::from_secs(2), fetch_user(42)).await;
}
When a future is dropped, its work is cancelled unless it was explicitly spawned elsewhere. That is a major conceptual difference from Java code that assumes executor-managed tasks continue until completion.
如果一个 future 被丢弃,它的工作通常也就跟着取消,除非这份工作已经被显式 spawn 到别处。这和很多 Java 代码默认“交给执行器之后它会自己跑完”的思路差别很大。
Spawning Background Work
派生后台任务
#![allow(unused)]
fn main() {
let handle = tokio::spawn(async move {
expensive_job().await
});
let value = handle.await.unwrap();
}
This is the closest match to scheduling work on an executor and retrieving the result later.
这和把任务提交给执行器、稍后再取结果的思路最接近。
Practical Advice for Java Developers
给 Java 开发者的实际建议
- Learn the difference between “constructing a future” and “driving a future”.
先把“创建 future”和“驱动 future 执行”这两个动作彻底分开。 - Reach for
join!,select!, andtimeoutearly; they cover most day-one patterns.
尽早熟悉join!、select!、timeout,入门阶段的大多数模式都离不开它们。 - Be careful with blocking APIs inside async code. Use dedicated blocking pools when needed.
在 async 代码里要小心阻塞 API,确实要阻塞时就切到专门的阻塞线程池。 - Treat async Rust as a separate runtime model, not as Java async with different syntax.
把 async Rust 当成一套独立运行时模型来看,不要把它当成“只是语法不同的 Java async”。
Once this clicks, Rust async stops feeling mysterious and starts feeling mechanically predictable.
这层一旦想通,Rust async 就不会再显得玄乎,反而会变得非常机械、非常可预测。
Unsafe Rust and FFI §§ZH§§ unsafe Rust 与 FFI
Unsafe Rust and FFI
Unsafe Rust 与 FFI
What you’ll learn: What
unsafeactually means in Rust, when Java teams typically need it, and how JNI, JNA, or Panama map onto Rust FFI.
本章将学习: Rust 里的unsafe到底意味着什么、Java 团队通常会在什么场景遇到它,以及 JNI、JNA、Panama 与 Rust FFI 的对应关系。Difficulty: 🔴 Advanced
难度: 🔴 高级
unsafe does not turn Rust into chaos mode. It marks code where the compiler can no longer verify every safety invariant. The job of the programmer becomes narrower and more explicit: document the invariant, confine the dangerous operation, and expose a safe API whenever possible.unsafe 不是把 Rust 一键切成失控模式。它只是标记出“编译器已经无法替代开发者验证全部安全约束”的代码区块。此时开发者的职责会变得更窄、更明确:写清约束、把危险操作关进小范围、尽可能向外暴露安全 API。
When Java Developers Usually Meet unsafe
Java 开发者通常会在什么时候碰到 unsafe
- wrapping a C library for use inside Rust
给 C 库写 Rust 包装层。 - exporting a Rust library so Java can call it
把 Rust 库导出给 Java 调用。 - working with raw buffers, shared memory, or kernel interfaces
处理原始缓冲区、共享内存或内核接口。 - implementing performance-sensitive data structures that cannot be expressed in fully safe code
实现一些性能敏感、无法完全用安全 Rust 表达的数据结构。
What unsafe Allows
unsafe 允许做什么
- dereferencing raw pointers
解引用裸指针。 - calling unsafe functions
调用 unsafe 函数。 - accessing mutable statics
访问可变静态变量。 - implementing unsafe traits
实现 unsafe trait。
Most real projects should keep unsafe in a tiny number of modules.
真实项目通常都应该把 unsafe 收敛在极少数模块里。
FFI Boundary: Java and Rust
FFI 边界:Java 与 Rust
The cleanest mental model is:
最容易记的心智模型是下面这张表:
| Java side Java 侧 | Rust side Rust 侧 |
|---|---|
| JNI, JNA, or Panama binding | extern "C" functions |
ByteBuffer or native memory segmentByteBuffer 或原生内存段 | raw pointer or slice 裸指针或切片 |
| Java object lifetime Java 对象生命周期 | explicit Rust ownership rules 显式 Rust 所有权规则 |
| exception and null conventions 异常与空值约定 | explicit return value or error code 显式返回值或错误码 |
Minimal Rust Export
最小 Rust 导出示例
#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
}
That symbol can then be called through a native interface on the Java side.
这样导出的符号就可以通过 Java 侧的原生互操作层调用。
Practical FFI Rules
FFI 实战规则
- Use a stable ABI such as
extern "C".
使用稳定 ABI,比如extern "C"。 - Do not let panics cross the FFI boundary.
不要让 panic 穿过 FFI 边界。 - Prefer plain integers, floats, pointers, and opaque handles at the boundary.
边界上优先使用整数、浮点、指针和 opaque handle 这类朴素类型。 - Convert strings and collections at the edge instead of trying to share high-level representations.
字符串和集合在边界处转换,别试图共享两边各自的高级表示。 - Free memory on the same side that allocated it.
谁分配内存,最好就由谁释放。
Opaque Handle Pattern
opaque handle 模式
#![allow(unused)]
fn main() {
pub struct Engine {
counter: u64,
}
#[no_mangle]
pub extern "C" fn engine_new() -> *mut Engine {
Box::into_raw(Box::new(Engine { counter: 0 }))
}
#[no_mangle]
pub extern "C" fn engine_increment(ptr: *mut Engine) -> u64 {
let engine = unsafe { ptr.as_mut() }.expect("null engine pointer");
engine.counter += 1;
engine.counter
}
#[no_mangle]
pub extern "C" fn engine_free(ptr: *mut Engine) {
if !ptr.is_null() {
unsafe { drop(Box::from_raw(ptr)); }
}
}
}
This pattern is far easier to reason about than trying to expose Rust structs field-by-field to Java code.
和把 Rust 结构体字段逐个暴露给 Java 相比,这种 opaque handle 模式要好理解得多,也更稳妥。
JNI, JNA, or Panama?
JNI、JNA、Panama 怎么选
- JNI offers full control, but the API is verbose.
JNI 控制力最强,但 API 很啰嗦。 - JNA is easier for quick integration, but adds overhead.
JNA 集成更快,但会带来额外开销。 - Panama is the long-term modern direction for native interop on newer JDKs.
Panama 则是较新 JDK 上更现代、也更值得关注的长期方向。
The Rust side stays mostly the same in all three cases. The biggest difference is how the Java layer loads symbols and marshals data.
对 Rust 侧来说,这三种方案的大体写法差不多。真正差异主要落在 Java 侧如何装载符号和封送数据。
Advice
建议
- Write the safe Rust API first.
先把安全 Rust API 设计好。 - Add the FFI layer second.
再加 FFI 包装层。 - Audit every pointer assumption.
把每一条指针假设都审一遍。 - Keep the boundary narrow and boring.
让边界尽量窄、尽量朴素。
That discipline is what turns unsafe from a liability into an implementation detail.
真正能把 unsafe 从负担变成实现细节的,靠的就是这套纪律。
Testing §§ZH§§ 测试
Testing in Rust vs Java
Rust 与 Java 的测试方式对照
What you’ll learn: How Rust testing maps to JUnit-style workflows, where parameterized tests fit, and how property testing and mocking compare to the Java ecosystem.
本章将学习: Rust 测试模型如何对应 JUnit 风格工作流、参数化测试放在什么位置,以及性质测试和 mock 手法与 Java 生态如何对应。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Rust testing feels much closer to library development than to framework-heavy test runners. The defaults are small and built in.
Rust 的测试模型更像贴着库开发本身展开,而不是围绕一个很重的测试框架转。默认能力很小、很直接,而且是内建的。
Unit Tests
单元测试
class CalculatorTest {
@org.junit.jupiter.api.Test
void addReturnsSum() {
assertEquals(5, new Calculator().add(2, 3));
}
}
#![allow(unused)]
fn main() {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::add;
#[test]
fn add_returns_sum() {
assert_eq!(add(2, 3), 5);
}
}
}
Test Layout Mapping
测试布局映射
| Java habit Java 习惯 | Rust habit Rust 习惯 |
|---|---|
src/test/java | inline #[cfg(test)] modules or tests/内联 #[cfg(test)] 模块或 tests/ 目录 |
| JUnit assertions | assert!, assert_eq!, assert_ne! |
| integration test module 集成测试模块 | files in tests/ |
| parameterized tests 参数化测试 | rstest crate |
| property testing libraries 性质测试库 | proptest or quickcheck |
| Mockito | mockall or handwritten trait-based fakesmockall 或手写基于 trait 的 fake |
Integration Tests
集成测试
#![allow(unused)]
fn main() {
// tests/api_smoke.rs
use my_crate::parse_user;
#[test]
fn parses_valid_payload() {
let input = r#"{"id":1,"name":"Ada"}"#;
assert!(parse_user(input).is_ok());
}
}
Integration tests compile as external consumers of the crate. That makes them a good match for “public API only” expectations.
集成测试会把 crate 当成外部消费者来编译,因此特别适合验证“公共 API 对外是否正常”这件事。
Async Tests
异步测试
#![allow(unused)]
fn main() {
#[tokio::test]
async fn fetch_user_returns_data() {
let result = fetch_user(42).await;
assert!(result.is_ok());
}
}
The mental model is straightforward: if production code needs a runtime, async tests need one too.
理解起来很简单:生产代码如果需要运行时,异步测试也需要运行时。
Property Testing
性质测试
Property testing is a strong fit for parsers, codecs, query builders, and data transformations.
解析器、编解码器、查询构造器、数据转换逻辑,这些都特别适合用性质测试补强。
#![allow(unused)]
fn main() {
use proptest::prelude::*;
proptest! {
#[test]
fn reversing_twice_returns_original(xs: Vec<i32>) {
let reversed: Vec<_> = xs.iter().copied().rev().collect();
let restored: Vec<_> = reversed.iter().copied().rev().collect();
prop_assert_eq!(xs, restored);
}
}
}
Advice for Java Teams
给 Java 团队的建议
- Keep fast unit tests close to the module they validate.
快速单元测试尽量贴着被测模块写。 - Add integration tests for crate boundaries, CLI behavior, and serialized formats.
crate 边界、CLI 行为、序列化格式这些地方要补上集成测试。 - Prefer trait-based seams for mocking instead of container-heavy indirection.
mock 时优先依靠 trait 边界,而不是把容器式间接层堆得很厚。 - Use property tests where one handwritten example is not enough.
凡是靠一两个手写样例压不住的逻辑,就考虑加性质测试。
Rust’s testing model feels lighter than a typical enterprise Java test stack, but that lightness is usually an advantage rather than a limitation.
Rust 的测试模型看起来比典型企业 Java 测试栈轻得多,但这种“轻”大多数时候反而是优点,不是短板。
Migration Patterns and Case Studies §§ZH§§ 迁移模式与案例研究
Migration Patterns and Case Studies
迁移模式与案例
What you’ll learn: How Java teams usually introduce Rust, which patterns translate cleanly, and where direct one-to-one translation is a trap.
本章将学习: Java 团队通常怎样引入 Rust、哪些模式转换起来比较顺手,以及哪些地方如果硬做一比一翻译基本都会踩坑。Difficulty: 🟡 Intermediate
难度: 🟡 中级
The best Java-to-Rust migration is usually selective, not total. Teams get the highest return by moving the parts that benefit most from native performance, memory control, or stronger correctness guarantees.
从 Java 迁到 Rust,最好的策略通常都是“选择性迁移”,而不是“整体替换”。真正回报最高的,是那些最吃原生性能、内存控制或强编译期正确性的模块。
Pattern Mapping
模式映射
| Java pattern Java 模式 | Rust direction Rust 方向 |
|---|---|
| service interface | trait plus concrete implementation trait 加具体实现 |
| builder | builder or configuration struct builder 或配置结构体 |
Optional<T> | Option<T> |
| exception hierarchy 异常层级 | domain error enum 领域错误枚举 |
| stream pipeline Stream 流水线 | iterator chain 迭代器链 |
| Spring bean wiring Spring Bean 装配 | explicit construction and ownership 显式构造与显式所有权 |
What Translates Cleanly
哪些东西迁起来比较顺
- DTOs and config types usually map well to Rust structs.
DTO 和配置类型通常很容易映射成 Rust struct。 - Validation logic often becomes simpler once null and exception paths are explicit.
校验逻辑在空值和错误路径显式化之后,往往会比原来更清爽。 - Data transformation code often improves when rewritten as iterator pipelines.
数据转换类代码在改写成迭代器流水线后,通常质量会更好。
What Usually Needs Redesign
哪些地方通常要重做设计
- inheritance-heavy service layers
高度依赖继承层级的服务层。 - frameworks that rely on reflection and runtime proxies
严重依赖反射和运行时代理的框架式写法。 - dependency injection patterns built around containers instead of explicit ownership
围绕容器展开、而不是围绕显式所有权展开的依赖注入模式。 - large exception hierarchies used as ambient control flow
把大型异常层级当成隐式控制流来使用的写法。
Case Study 1: Native Helper Library
案例一:原生辅助库
A Java service keeps its core business logic on the JVM but calls a Rust library for parsing, compression, or protocol processing. This is often the lowest-friction starting point because the Java service boundary remains stable while the hot path moves to native code.
一种很常见的路线是:主业务逻辑继续留在 JVM 上,只把解析、压缩、协议处理之类的热点路径交给 Rust 库。这类起点通常摩擦最小,因为 Java 服务边界保持稳定,但性能热点已经转移到了原生层。
Case Study 2: Replace a CLI or Background Agent
案例二:替换 CLI 或后台 agent
Command-line tools, migration helpers, log processors, and small background agents are ideal Rust candidates. They benefit from:
命令行工具、迁移辅助程序、日志处理器、小型后台 agent 这些东西,通常都很适合先交给 Rust:
- tiny deployment footprint
部署体积小。 - predictable memory use
内存使用可预测。 - easy static linking in container-heavy environments
在容器密集环境里更容易做静态交付。
Case Study 3: Move a Gateway or Edge Component
案例三:迁移网关或边缘组件
Teams sometimes rewrite a proxy, rate limiter, or stream processor in Rust while the rest of the platform stays in Java. This works well when tail latency and resource efficiency matter more than framework convenience.
还有一条路线是把代理、限流器、流处理器这类边缘组件改成 Rust,而平台主体继续保留在 Java。只要关注点在尾延迟和资源效率,这种拆法通常很划算。
Migration Rules That Save Pain
真正能省事的迁移规则
- Move a boundary, not an entire monolith.
迁移一个边界,不要试图一口吞掉整个单体。 - Pick one success metric up front: latency, memory, startup time, or bug class elimination.
先选定一个成功指标,比如延迟、内存、启动时间,或者某类 bug 的消失。 - Keep serialization formats and contracts stable during the first migration phase.
第一阶段尽量保持序列化格式和接口契约稳定。 - Let Rust own the components that benefit from stronger invariants.
让 Rust 去接管那些最依赖强约束的模块。 - Do not translate Java framework patterns blindly; redesign them around traits, enums, and explicit construction.
别把 Java 框架模式生搬硬套过来,要围绕 trait、enum 和显式构造去重新组织。
A Good First Project
一个合适的第一站项目
Pick one of these:
下面几类都很适合作为第一站:
- a parser or validator library
解析器或校验器库。 - a CLI tool currently written in Java
当前由 Java 编写的命令行工具。 - a background worker that spends most of its time transforming bytes or JSON
主要工作是处理字节流或 JSON 的后台 worker。 - an edge-facing network component with strict latency goals
对延迟目标要求严格的边缘网络组件。
That path teaches Cargo, ownership, error handling, testing, and deployment without forcing the whole organization into a risky rewrite.
这条路能把 Cargo、所有权、错误处理、测试和部署全都带一遍,同时又不用把整个组织拖进高风险重写里。
Essential Crates for Java Developers §§ZH§§ Java 开发者必备 crate
Essential Crates for Java Developers
Java 开发者常用的 Rust crate
What you’ll learn: Which Rust crates map most naturally to familiar Java engineering needs, how to choose them without rebuilding the entire Spring universe, and which combinations make sense for services, tools, and libraries.
本章将学习: 哪些 Rust crate 最适合映射 Java 开发里熟悉的工程需求,怎样在不重造整套 Spring 世界的前提下做选择,以及服务、工具、库项目分别适合什么组合。Difficulty: 🟡 Intermediate
难度: 🟡 中级
There is no perfect one-to-one mapping between Rust crates and Java libraries. Rust ecosystems are usually smaller, more focused, and less framework-centered. The useful question is not “what is the exact Rust version of library X?” but “which crate category solves the same engineering problem with Rust-style composition?”
Rust crate 和 Java 库之间通常不存在完美的一比一映射。Rust 生态一般更小、更聚焦,也没那么强的框架中心化倾向。真正有用的问题不是“某个 Java 库在 Rust 里一模一样的替身是谁”,而是“哪类 crate 用更符合 Rust 习惯的组合方式解决同一种工程问题”。
Practical Mapping Table
实用映射表
| Java need Java 需求 | Typical Java choice | Common Rust choice |
|---|---|---|
| JSON serialization JSON 序列化 | Jackson, Gson | serde, serde_json |
| HTTP client HTTP 客户端 | HttpClient, OkHttp | reqwest |
| async runtime 异步运行时 | CompletableFuture plus executors | tokio |
| web framework Web 框架 | Spring MVC, Spring WebFlux, Javalin | axum, actix-web, warp |
| logging and observability 日志与可观测性 | SLF4J, Logback, Micrometer | tracing, tracing-subscriber, metrics |
| configuration 配置 | Spring config, Typesafe config | config, figment |
| CLI parsing 命令行解析 | picocli | clap |
| database access 数据库访问 | JDBC, JPA, jOOQ | sqlx, diesel, sea-orm |
| gRPC gRPC | gRPC Java | tonic |
| testing helpers 测试辅助 | JUnit ecosystem | built-in #[test], rstest, proptest |
Starter Sets by Project Type
按项目类型推荐的起步组合
HTTP Service
HTTP 服务
[dependencies]
axum = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["trace", "cors"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
That bundle feels familiar to Spring Boot developers: routing, middleware, JSON, runtime, and structured logs.
这套组合对 Spring Boot 开发者来说会比较有熟悉感:路由、中间件、JSON、运行时、结构化日志,基本都齐了。
CLI or Internal Tool
命令行工具或内部工具
[dependencies]
anyhow = "1"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
toml = "0.8"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
That is often enough for the kind of command-line tools Java teams would previously build with picocli and a small utility stack.
对很多原本会用 picocli 加一小堆工具库去写的 Java 命令行程序来说,这套已经很够用了。
Selection Heuristics for Java Teams
Java 团队的 crate 选择原则
- prefer crates with strong documentation, recent releases, and active issue triage
优先挑文档扎实、版本更新正常、问题处理活跃的 crate。 - choose libraries that compose well instead of frameworks that hide every decision
优先选能自由组合的库,而不是把所有决定都包起来的大框架。 - read feature flags before enabling
fulleverywhere, because compile-time surface area matters more in Rust
别一上来就全开full,Rust 的编译面和依赖面比 Java 更值得细抠。 - prefer explicit types and thin abstractions before introducing dependency-injection-like indirection
先接受显式类型和薄抽象,再考虑类似依赖注入那种间接层。
Common Migration Patterns
常见迁移思路
From Spring Boot Thinking
从 Spring Boot 思维迁过来
Java teams often look for one dependency that supplies controllers, dependency injection, validation, config binding, metrics, and database access in a single package. Rust usually works better with a smaller kit:
Java 团队常常会下意识寻找一个依赖,最好把控制器、依赖注入、校验、配置绑定、指标、数据库访问全包了。Rust 往往更适合一套小而清楚的组合:
axumfor routing and handlersaxum负责路由和处理函数。towerortower-httpfor middlewaretower或tower-http负责中间件。serdefor JSON and config shapesserde负责 JSON 和配置结构。sqlxfor database accesssqlx负责数据库访问。tracingfor logs and spanstracing负责日志和 span。
That stack is less magical than Spring Boot, but it is also easier to debug because each part stays visible.
这套东西没有 Spring Boot 那么“全自动”,但调试起来反而更清楚,因为每一层都摆在明面上。
From JPA Thinking
从 JPA 思维迁过来
Rust developers often start with sqlx because it keeps SQL explicit and checks queries more aggressively. Teams that want a more ORM-like experience can evaluate diesel or sea-orm, but the first migration usually goes smoother when the data layer stays close to SQL.
Rust 开发里,很多人一开始会先选 sqlx,因为它让 SQL 保持显式,而且对查询的检查更直接。想要更 ORM 一点的体验,也可以评估 diesel 或 sea-orm;不过第一轮迁移通常还是让数据层贴近 SQL 会更顺。
Where Java Developers Commonly Overbuild
Java 开发者最容易搭过头的地方
- recreating dependency injection containers before understanding ownership and constructor-based composition
还没理解所有权和基于构造参数的组合,就急着重建依赖注入容器。 - reaching for ORM-style abstraction before modeling the actual data flow
还没把真实数据流建清楚,就先上 ORM 式大抽象。 - assuming every cross-cutting concern needs a framework extension point
总觉得每个横切问题都必须挂在框架扩展点上。 - building custom platform layers before learning the standard Cargo workflow
Cargo 的标准工作方式还没熟,就先自建平台层和包装层。
Recommended First Wave
建议优先掌握的第一批 crate
For most teams, these are the first crates worth mastering:
对大多数团队来说,先把下面这些用顺最值:
serdetokioaxumorreqwest, depending on whether the project is server-side or client-sideaxum或reqwest,取决于项目偏服务端还是客户端。tracingthiserrorandanyhowthiserror和anyhow。clap
Once those are comfortable, the rest of the ecosystem becomes much easier to evaluate without importing Java habits that Rust does not benefit from.
这些掌握之后,再去看更大的生态就容易多了,也更不容易把对 Rust 没好处的 Java 习惯一并带过来。
Incremental Adoption Strategy §§ZH§§ 渐进式引入策略
Incremental Adoption Strategy
渐进式引入策略
What you’ll learn: How a Java organization can introduce Rust gradually, which workloads make the best first candidates, and how to sequence skills, tooling, and production rollout.
本章将学习: Java 团队如何渐进式地把 Rust 引入组织,哪些负载最适合作为第一批目标,以及能力、工具链、生产发布该怎样排顺序。Difficulty: 🟡 Intermediate
难度: 🟡 中级
The wrong slogan is “rewrite the monolith in Rust.”
最容易把事情带偏的口号,就是“用 Rust 重写整个单体系统”。
The healthy path is staged adoption:
更健康的路径是分阶段引入:
- learn on contained workloads
先在边界清晰的小工作负载上学习。 - deploy one focused service or worker
再上线一个聚焦的服务或 worker。 - expand only after tooling and operations are stable
等工具链和运维流程稳定之后再扩大范围。
Good First Targets
适合作为第一批目标的工作负载
| Candidate 候选目标 | Why it works well 为什么合适 |
|---|---|
| CLI or scheduled batch job CLI 或定时批处理任务 | easy deployment and rollback 部署、回滚都简单 |
| CPU-heavy worker CPU 密集型 worker | Rust often wins on latency and memory Rust 在延迟和内存上往往更有优势 |
| narrow microservice 边界清晰的小型微服务 | HTTP or Kafka contract is easy to freeze HTTP 或 Kafka 契约容易冻结 |
| gateway or adapter 网关或协议适配器 | explicit I/O fits Rust well 显式 I/O 非常适合 Rust |
Bad first targets usually look like this:
不适合作为第一枪的目标通常长这样:
- a huge Spring Boot monolith
一个巨大无比的 Spring Boot 单体。 - modules full of reflection and proxy magic
充满反射和代理魔法的模块。 - components with weak tests and many owners
测试薄弱且多人共同维护的组件。
Three Common Integration Styles
三种常见接入方式
1. Separate Service or Sidecar
独立服务或 Sidecar
Spring Boot keeps calling over HTTP or gRPC, while Rust owns one focused workload.
Spring Boot 继续通过 HTTP 或 gRPC 调用,Rust 则接管一个聚焦的工作负载。
This is usually the best first production move.
这通常是最适合作为第一步生产接入的方式。
2. Queue Worker
队列消费型 Worker
If the organization already uses Kafka or RabbitMQ, a Rust consumer is often even easier than a public HTTP service.
如果组织已经用了 Kafka 或 RabbitMQ,那么 Rust 消费者往往比新开一个公共 HTTP 服务还更容易上手。
3. JNI or Native Embedding
JNI 或原生嵌入
Useful in some cases, but rarely the first step.
某些场景里当然有用,但几乎不该作为第一步。
Packaging, debugging, and ownership boundaries all become harder.
打包、调试、所有权边界,都会一下子变得更难。
A 90-Day Plan
一个 90 天引入计划
Days 1-30: Foundation
第 1 到 30 天:打基础
- ownership, borrowing,
Result, andOption
把所有权、借用、Result、Option先学扎实。 - standardize
cargo fmt,clippy, and tests
把cargo fmt、clippy、测试命令统一下来。 - write small internal exercises
写几组内部小练习。
Days 31-60: One Real Service
第 31 到 60 天:做一个真实服务
- choose one bounded workload
挑一个边界清晰的工作负载。 - add config, logs, health checks, metrics
把配置、日志、健康检查、指标都补齐。 - make sure the team can operate it
重点不是能跑,而是团队真能运维它。
Days 61-90: Expand Carefully
第 61 到 90 天:谨慎扩大
- define review checklists
定义代码审查清单。 - define crate layout conventions
定义 crate 布局规范。 - define error-handling conventions
定义错误处理规范。
That is when Rust stops being an experiment and starts becoming an engineering capability.
走到这里,Rust 才算从实验品变成了组织真正掌握的工程能力。
Operational Readiness
运维就绪清单
Before expanding Rust usage, the first service should already have:
在继续扩展 Rust 之前,第一批服务最好已经具备下面这些能力:
- tracing or structured logging
追踪或结构化日志。 - health and readiness endpoints
健康检查和就绪探针。 - metrics export
指标导出。 - reproducible builds
可复现构建。 - integration tests against the real boundary
对真实边界的集成测试。
If these are missing, the organization is learning syntax but not learning production engineering.
如果这些都没有,那组织学到的只是语法,不是生产工程能力。
Spring and Spring Boot Migration §§ZH§§ Spring 与 Spring Boot 迁移
Spring and Spring Boot Migration
Spring 与 Spring Boot 迁移
What you’ll learn: How Spring and Spring Boot concepts translate into idiomatic Rust service architecture, which Rust libraries usually replace familiar Spring features, and how to migrate one service without trying to clone the whole Spring ecosystem.
本章将学习: Spring 与 Spring Boot 的核心概念如何迁移到符合 Rust 习惯的服务架构中,哪些 Rust 库通常会替代熟悉的 Spring 功能,以及怎样迁移一个服务而不是妄图克隆整个 Spring 生态。Difficulty: 🟡 Intermediate
难度: 🟡 中级
The biggest Spring-to-Rust mistake is searching for “the Rust Spring Boot.”
从 Spring 迁到 Rust 时,最大的误区就是拼命去找“Rust 版 Spring Boot”。
Rust web development is more toolkit-oriented and much less centered around one giant framework.
Rust 的 Web 开发生态更偏工具箱式组合,而不是围绕一个超级框架打天下。
Concept Mapping
概念映射
| Spring concept Spring 概念 | Common Rust direction Rust 常见对应方向 |
|---|---|
@RestController | axum or actix-web handlersaxum 或 actix-web 的 handler |
| dependency injection container | explicit construction plus shared state 显式构造加共享状态 |
@ConfigurationProperties | config structs plus env/file loading 配置结构体加环境变量或文件加载 |
| servlet filter chain | tower middlewaretower 中间件 |
@ControllerAdvice | IntoResponse or top-level error mappingIntoResponse 或顶层错误映射 |
JpaRepository | sqlx, sea-orm, or handwritten repositoriessqlx、sea-orm 或手写 repository |
What Changes the Most
变化最大的地方
1. Dependency Wiring Becomes Explicit
依赖装配会变得显式
#![allow(unused)]
fn main() {
#[derive(Clone)]
struct AppState {
user_service: UserService,
audit_service: AuditService,
}
}
Spring relies on the container to build the object graph.
Spring 依赖容器去构建对象图。
Rust usually wants that wiring visible in startup code.
Rust 更希望这套装配逻辑直接写在启动代码里,让人一眼看明白。
2. Reflection Stops Being the Center
反射不再处在系统中心
Spring leans heavily on annotations, proxies, and runtime discovery.
Spring 很依赖注解、代理和运行时发现机制。
Rust ecosystems usually prefer:
Rust 生态通常更偏好:
- derives for data boilerplate
用 derive 处理数据样板代码。 - middleware for cross-cutting concerns
用中间件承接横切关注点。 - plain functions plus explicit types
用普通函数和显式类型完成主逻辑。
3. Data Access Gets More Honest
数据访问会变得更诚实
Spring Boot teams often carry JPA habits such as inferred repositories and hidden query behavior.
Spring Boot 团队往往会带着 JPA 的思维惯性,比如推断式 repository 和隐藏的查询行为。
Rust teams usually choose earlier between explicit SQL, a lighter ORM, or a small handwritten repository layer.
Rust 团队通常会更早在显式 SQL、较轻的 ORM、或小型手写 repository 层之间做选择。
For migration work, sqlx is often the easiest mental reset.
在迁移场景里,sqlx 往往是最容易把脑子掰正过来的选择。
Typical Service Shape
典型服务形态
Spring Boot often looks like this:
Spring Boot 常见结构是这样:
controller -> service -> repository -> database
Rust often looks like this:
Rust 服务更常见的是这样:
router -> handler -> service -> repository -> database
The key change is not the number of layers; it is the disappearance of hidden framework behavior.
真正的关键变化不在于层数,而在于隐藏的框架行为被拿掉了。
From Controller to Handler
从 Controller 到 Handler
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable UUID id) {
return userService.getUser(id);
}
}
#![allow(unused)]
fn main() {
async fn get_user(
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<Json<UserResponse>, AppError> {
let user = state.user_service.get_user(id).await?;
Ok(Json(user))
}
}
The handler is just a function, which is a huge part of why Rust services feel easier to trace.
handler 就是一个普通函数,这也是 Rust 服务为什么会显得更容易追踪的一大原因。
Practical Advice
实用建议
- keep the HTTP contract stable first
先把 HTTP 契约稳住。 - migrate one bounded context at a time
一次迁一个边界清晰的上下文。 - move business rules before chasing framework parity
先搬业务规则,再谈框架表面对齐。 - do not rebuild Spring in macros
别想着用宏把 Spring 整个再造一遍。
Rust migration works best when the result is a good Rust service, not a bitter imitation of a Spring service.
迁移最成功的状态,是最后得到一个好的 Rust 服务,而不是一个带着怨气模仿 Spring 的 Rust 程序。
Best Practices and Reference §§ZH§§ 最佳实践与参考
Best Practices and Reference
最佳实践与参考
What you’ll learn: The habits that help Java developers write more idiomatic Rust instead of mechanically translating old patterns.
本章将学习: 哪些习惯能帮助 Java 开发者写出更符合 Rust 气质的代码,而不是机械翻译旧模式。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Prefer Explicit Ownership
优先写清所有权
Pass borrowed data when ownership is not needed. Return owned data when the caller should keep it.
不需要所有权时就传借用,需要调用方长期持有时再返回拥有所有权的值。
Design Small Public APIs
公开 API 尽量小
Default privacy is an advantage. Use it to keep module boundaries narrow.
默认私有性是优势,用它把模块边界压窄。
Model Variants with Enums
变体优先考虑 enum
If a Java design would reach for an inheritance hierarchy only to represent alternatives, consider an enum first.
如果 Java 设计里那棵继承树只是为了表示几种分支,那在 Rust 里先想 enum。
Keep Error Types Honest
错误类型要诚实
Use domain enums or precise error wrappers instead of hiding everything behind generalized exceptions too early.
优先使用领域错误枚举或精确错误包装,不要太早把一切都塞进泛化错误里。
Use Concrete Types Until Abstraction Is Earned
抽象要靠事实争取
Many Java developers abstract too early because frameworks encourage it. In Rust, concrete code often stays cleaner longer.
很多 Java 开发者会因为框架文化而过早抽象。Rust 里具体代码通常能更久地保持整洁。
Let the Compiler Participate
让编译器参与设计
Compiler feedback is not just about fixing syntax. It is often feedback on ownership design, borrowing scope, API shape, and error flow.
编译器反馈不只是语法纠错,它往往也在反馈所有权设计、借用范围、API 形状和错误流。
Idiomatic Rust usually feels smaller, stricter, and less ceremonial than enterprise Java. That is a feature, not a deficit.
符合 Rust 习惯的代码通常比企业 Java 更小、更严、更少仪式感。这是特性,不是短板。
Performance Comparison and Migration §§ZH§§ 性能对比与迁移
Performance Comparison and Migration
性能比较与迁移
What you’ll learn: How to think honestly about JVM performance versus Rust native performance and when migration is actually justified.
本章将学习: 如何诚实地比较 JVM 性能和 Rust 原生性能,以及什么时候迁移才真的说得过去。Difficulty: 🟡 Intermediate
难度: 🟡 中级
Rust often wins on startup time, memory footprint, and tail-latency predictability. Java often wins on mature libraries, team familiarity, and framework productivity.
Rust 通常在启动时间、内存占用和尾延迟可预测性上更强;Java 则常常在成熟库、团队熟练度和框架生产力上更占优。
Where Rust Usually Wins
Rust 通常更强的地方
- startup time
启动时间。 - binary distribution simplicity
二进制分发更简单。 - memory footprint
内存占用更小。 - predictable latency under load
负载下延迟更可预测。
Where Java Still Holds Up Well
Java 依然很能打的地方
- large business systems with mature Spring-based workflows
基于 Spring 的大型业务系统。 - teams optimized for JVM tooling and operations
已经围绕 JVM 工具和运维体系优化好的团队。 - applications where throughput is fine and developer speed matters more than native efficiency
吞吐已经够用,而开发速度比原生效率更重要的应用。
Migration Rule
迁移规则
Benchmark the actual workload before declaring victory. Replace hype with measurements:
先测真实负载,再谈胜利。别拿情绪和口号替代测量:
- p50 and p99 latency
p50 和 p99 延迟。 - memory use
内存占用。 - startup time
启动时间。 - deployment complexity
部署复杂度。
Rust is strongest when it solves a concrete operational pain, not when it is adopted as an aesthetic preference.
Rust 最强的时候,是它在解决一个明确的运行问题,而不是被当成审美选择时。
Learning Path and Resources §§ZH§§ 学习路径与资源
Learning Path and Next Steps
学习路径与下一步
What you’ll learn: A structured Rust learning plan tailored for experienced Java developers, the concept pairs that matter most during migration, and a resource stack that supports moving from language study to real service work.
本章将学习: 一套面向已有 Java 经验开发者的 Rust 学习路线,迁移过程中最重要的概念映射,以及从语言学习走向真实服务开发时该依赖的资源组合。Difficulty: 🟢 Beginner
难度: 🟢 入门
The fastest way for a Java developer to learn Rust is to map familiar Java concepts to Rust concepts in the right order.
Java 开发者学习 Rust 最快的方式,不是从零散知识点乱看,而是把熟悉的 Java 概念按正确顺序映射到 Rust 概念上。
The Six Concept Pairs That Matter Most
最关键的六组概念映射
| Java habit Java 习惯 | Rust replacement Rust 对应物 |
|---|---|
null and Optional<T>null 与 Optional<T> | Option<T> |
| exceptions 异常 | Result<T, E> |
| mutable object references 可变对象引用 | ownership and borrowing 所有权与借用 |
| interfaces 接口 | traits |
| class hierarchies 类继承层级 | struct + enum + compositionstruct、enum、组合 |
| Spring container wiring Spring 容器装配 | explicit state and constructors 显式状态与构造 |
If these six pairs feel natural, most later Rust topics become easier.
如果这六组映射真的顺手了,后面大多数 Rust 主题都会轻松很多。
An 8-Week Plan
一个 8 周学习计划
Weeks 1-2
第 1 到 2 周
Stringvs&str
搞清楚String和&str。- move vs borrow
搞清楚移动和借用。 Option<T>andResult<T, E>
掌握Option<T>与Result<T, E>。
Weeks 3-4
第 3 到 4 周
enumandmatch
重点学enum和match。- traits as interface-like behavior
把 trait 理解成接口式行为抽象。 Vec,HashMap, iterators
掌握Vec、HashMap、迭代器。
Weeks 5-6
第 5 到 6 周
- crate-level error enums
crate 级错误枚举。 thiserrorandanyhowthiserror与anyhow的分工。tokioand async basicstokio和异步基础。
Weeks 7-8
第 7 到 8 周
axumoractix-webaxum或actix-web。- configuration, tracing, metrics
配置、追踪、指标。 - handler/service/repository boundaries
handler、service、repository 的边界划分。
Suggested Project Ladder
建议的项目阶梯
- log or CSV transformation tool
日志或 CSV 转换工具。 - JSON validation job
JSON 校验任务。 - external API client
外部 API 客户端。 - small HTTP service
一个小型 HTTP 服务。 - Spring Boot endpoint migration
迁移一个 Spring Boot 接口。
This ordering matters because jumping into async web services too early usually mixes framework confusion with ownership confusion.
这个顺序非常重要,因为过早跳进异步 Web 服务,往往会把框架困惑和所有权困惑混成一锅粥。
Resource Stack
资源组合
- The Rust Programming Language
官方 Rust 书,最核心。 - Rust by Example
适合快速看小例子。 - Rustlings
适合形成基本手感。 serdedocs
做 JSON 和数据模型时非常重要。tokiotutorial
理解异步运行时的最好起点之一。axumguide
做 Web 服务时非常贴近实战。
Common Traps
常见陷阱
Trap 1: Treating the Borrow Checker as the Enemy
误区一:把借用检查器当成敌人
The borrow checker is exposing aliasing and mutation truth, not randomly blocking progress.
借用检查器是在揭示别名与可变性的真实约束,不是在随机刁难人。
Trap 2: Recreating Inheritance Everywhere
误区二:到处重建继承体系
If a closed set of cases is modeled with traits only, an enum is often missing.
如果一个封闭变体集合全靠 trait 去建模,那往往说明 enum 该出现却没出现。
Trap 3: Learning Async Before Ownership
误区三:还没懂所有权就急着学异步
Async Rust is much easier after ownership, moves, and Result already feel normal.
只有当所有权、移动、Result 这些基础已经顺手了,Async Rust 才会真正容易起来。
Ready-for-Real-Work Milestone
进入真实迁移工作的里程碑
- can model domain states with
enum
能用enum建模领域状态。 - can explain ownership and borrowing clearly
能把所有权和借用讲明白。 - can define crate-level error types
能设计 crate 级错误类型。 - can build a small HTTP service with shared state
能写带共享状态的小型 HTTP 服务。 - can compare a Spring Boot endpoint with its Rust equivalent
能把一个 Spring Boot 接口和它的 Rust 对应物讲清差异。
Rust Tooling for Java Developers §§ZH§§ 面向 Java 开发者的 Rust 工具
Rust Tooling for Java Developers
面向 Java 开发者的 Rust 工具
What you’ll learn: Which everyday Rust tools correspond to the workflow Java developers already know from IDEs, formatters, linters, test runners, release pipelines, and debugging setups.
本章将学习: 日常 Rust 工具分别对应 Java 开发者熟悉的哪些 IDE、格式化、静态分析、测试、发布流水线和调试方式。Difficulty: 🟢 Beginner
难度: 🟢 初级
Rust tooling feels smaller than the Java ecosystem, but the essentials are strong and unusually coherent. Many Java teams are used to stitching together Maven or Gradle, IDE plugins, code style plugins, test runners, and release helpers. Rust trims a lot of that surface area.
Rust 工具体系看起来比 Java 生态小很多,但核心工具很硬,而且整体协同性非常强。很多 Java 团队习惯把 Maven 或 Gradle、IDE 插件、代码风格插件、测试插件、发布辅助工具拼成一套;Rust 在这方面会收得更紧,工具面更小。
Core Tool Mapping
核心工具映射
| Java workflow Java 工作流 | Rust tool |
|---|---|
| IDE language service IDE 语言服务 | rust-analyzer |
| formatter 格式化器 | rustfmt |
| static analysis 静态分析 | clippy |
| build and test command 构建与测试命令 | cargo |
| documentation generation 文档生成 | cargo doc |
| benchmark harness 基准测试 | criterion |
| extended test runner 增强测试执行器 | cargo-nextest |
| dependency or policy checks 依赖与策略检查 | cargo-deny, cargo-audit |
The Daily Loop
日常循环
cargo fmt
cargo clippy --all-targets --all-features
cargo test
cargo run
That loop replaces a surprising amount of Maven, Gradle, IDE, and plugin ceremony.
就这么一套循环,往往就能替掉不少 Maven、Gradle、IDE 插件层面的繁琐动作。
IDE Experience
IDE 体验
Java developers usually compare everything to IntelliJ IDEA. The closest Rust equivalent is rust-analyzer integrated into an editor or IDE. It gives:
Java 开发者通常会拿 IntelliJ IDEA 当标尺。Rust 里最接近的核心能力就是集成在编辑器或 IDE 里的 rust-analyzer,它可以提供:
- type information
类型信息。 - go to definition
跳转定义。 - inline diagnostics
内联诊断。 - rename and refactor support
重命名和基础重构支持。 - inlay hints that make ownership and lifetimes easier to read
能让所有权和生命周期更容易阅读的 inlay hint。
For mixed Java and Rust teams, it is common to keep IntelliJ IDEA for JVM work and use RustRover or another rust-analyzer-backed editor for Rust-heavy code.
对同时做 Java 和 Rust 的团队来说,常见做法是 JVM 侧继续用 IntelliJ IDEA,Rust 较多的仓库则配 RustRover 或其他基于 rust-analyzer 的编辑器。
rustfmt
rustfmt
Rust formatting culture is stricter than the average Java codebase. That usually helps teams move faster because formatting stops being a topic of debate.
Rust 对格式化一致性的要求通常比平均 Java 代码库更强。这反而经常能让团队更快,因为格式问题基本不会再成为争论点。
clippy
clippy
clippy is the tool that makes many new Rust developers improve quickly. It catches:clippy 往往是新 Rust 开发者进步最快的工具之一,它能抓出很多典型问题:
- needless clones
没必要的 clone。 - awkward iterator usage
别扭的迭代器写法。 - manual patterns that already have standard helpers
明明标准库已经有助手函数,却还在手搓。 - suspicious API design choices
可疑的 API 设计。 - common ownership mistakes that still compile but read poorly
虽然能编译,但读起来很别扭的典型所有权写法。
cargo doc
cargo doc
cargo doc generates local HTML documentation from code comments and public items. It is especially useful in library-heavy codebases where type-driven design matters.cargo doc 会根据代码注释和公共项生成本地 HTML 文档。对库很多、而且比较依赖类型设计的代码库来说,这玩意非常实用。
Testing and Debugging
测试与调试
Java developers often expect JUnit, Mockito, IDE test runners, and rich debugger integration. In Rust:
Java 开发者通常已经习惯了 JUnit、Mockito、IDE 测试运行器以及比较成熟的调试器集成。到了 Rust 这边,常见工具会变成:
cargo testis the default test entry pointcargo test是默认测试入口。cargo-nextestis useful when test suites become large
测试规模变大之后,cargo-nextest会更舒服。instahelps with snapshot-style assertionsinsta适合做快照式断言。tokio-consolehelps inspect async behavior in Tokio applicationstokio-console适合观察 Tokio 异步程序的运行状态。
The debugging story is simpler than Java’s JVM tooling, but the compiler catches much more before the debugger even becomes necessary.
调试生态肯定没有 JVM 那么厚,但 Rust 编译器会在调试器登场之前先替人挡掉更多问题。
Release and CI Tooling
发布与 CI 工具
For Java teams, this is the rough translation:
如果用 Java 团队熟悉的方式去理解,大致可以这样对照:
| Java habit Java 习惯 | Rust equivalent |
|---|---|
mvn verify or gradle check in CICI 里跑 mvn verify 或 gradle check | cargo fmt --check, cargo clippy, cargo test |
| dependency policy plugins 依赖策略插件 | cargo-deny, cargo-audit |
| generated API docs in pipeline 流水线生成 API 文档 | cargo doc |
| multi-module release automation 多模块发布自动化 | workspace-aware cargo commands, optionally cargo-dist |
Many teams also use cross when building for multiple targets from one CI environment.
如果需要从同一套 CI 环境构建多个目标平台,很多团队也会加上 cross。
Advice
建议
- Put
cargo fmt,cargo clippy, andcargo testin CI early.
尽早把cargo fmt、cargo clippy和cargo test放进 CI。 - Treat compiler diagnostics as part of the design process rather than as late feedback.
把编译器诊断当成设计过程的一部分,而不是项目后期的补救反馈。 - Keep the toolchain simple instead of layering custom wrappers too soon.
工具链先保持简洁,别太早在外面再套一堆自定义包装层。 - Standardize one workspace command set before inventing organization-specific build conventions.
先把团队统一的 workspace 命令集定清楚,再谈组织内部那套额外构建规范。
The pleasant surprise for many Java developers is that Rust tooling often feels more coherent because the ecosystem grew around Cargo and the compiler rather than around many competing build traditions.
很多 Java 开发者最后会有个挺舒服的感受:Rust 工具链往往更整齐,因为整个生态是围绕 Cargo 和编译器长出来的,而不是围绕多套彼此竞争的构建传统拼出来的。
Capstone Project: Migrate a Spring Boot User Service §§ZH§§ 综合项目:迁移一个 Spring Boot 用户服务
Capstone Project: Migrate a Spring Boot User Service
综合项目:迁移一个 Spring Boot 用户服务
What you’ll learn: How to migrate a small Spring Boot user service into a Rust web service step by step, preserving the HTTP contract while changing the implementation model from container-driven Java to explicit Rust composition.
本章将学习: 如何一步一步把一个小型 Spring Boot 用户服务迁移成 Rust Web 服务,在保持 HTTP 契约稳定的前提下,把实现方式从 Java 容器驱动切换成 Rust 的显式组合。Difficulty: 🔴 Advanced
难度: 🔴 高级
This capstone is intentionally shaped like real Java backend work.
这个综合项目故意做成贴近真实 Java 后端工作的形态,而不是玩具示例。
The source service contains:
原始服务包含下面这些内容:
GET /users/{id}
查询用户接口。POST /users
创建用户接口。- request validation
请求校验。 - repository layer
repository 层。 - JSON request and response DTOs
JSON 请求和响应 DTO。
Source Shape in Spring Boot
Spring Boot 版本的结构
controller -> service -> repository -> database
Typical pieces include @RestController, @Service, @Repository, request DTOs, and response DTOs.
典型组成就是 @RestController、@Service、@Repository、请求 DTO、响应 DTO 这些老熟人。
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable UUID id) {
return userService.getUser(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse createUser(@RequestBody CreateUserRequest request) {
return userService.createUser(request);
}
}
Target Shape in Rust
Rust 版本的目标结构
router -> handler -> service -> repository -> database
Suggested crates:
建议使用的 crate 组合:
[dependencies]
axum = "0.8"
serde = { version = "1", features = ["derive"] }
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "uuid"] }
tokio = { version = "1", features = ["full"] }
thiserror = "2"
uuid = { version = "1", features = ["serde", "v4"] }
Step 1: Freeze the Contract
第一步:先冻结接口契约
Before changing implementation details, lock down:
在动实现之前,先把这些东西钉死:
- route paths
路由路径。 - JSON payload shapes
JSON 载荷结构。 - status codes
状态码。 - validation rules
校验规则。
If the contract changes during migration, debugging becomes muddy immediately.
如果迁移过程中连契约都在乱变,排查问题时马上就会变得一团糟。
Step 2: Design the Rust Layout
第二步:先设计 Rust 模块布局
src/
main.rs
config.rs
error.rs
http/
handlers.rs
domain/
user.rs
repository/
user_repository.rs
service/
user_service.rs
This keeps the familiar layered feeling without copying Spring stereotypes one by one.
这样既保留了 Java 团队熟悉的分层感,又不会逐个复制 Spring stereotype。
Step 3: Separate DTOs and Domain Types
第三步:把 DTO 和领域对象分开
#![allow(unused)]
fn main() {
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub email: String,
pub display_name: String,
}
#[derive(Debug, Serialize)]
pub struct UserResponse {
pub id: Uuid,
pub email: String,
pub display_name: String,
}
#[derive(Debug, Clone)]
pub struct User {
pub id: Uuid,
pub email: String,
pub display_name: String,
}
}
The request and response shapes belong to the HTTP boundary, while the domain type belongs to business logic.
请求和响应结构属于 HTTP 边界,领域对象属于业务逻辑内部,这两者就应该分开。
Step 4: Validate During Conversion
第四步:在转换阶段显式校验
#![allow(unused)]
fn main() {
pub struct NewUser {
pub email: String,
pub display_name: String,
}
impl TryFrom<CreateUserRequest> for NewUser {
type Error = AppError;
fn try_from(value: CreateUserRequest) -> Result<Self, Self::Error> {
let email = value.email.trim().to_ascii_lowercase();
let display_name = value.display_name.trim().to_string();
if !email.contains('@') {
return Err(AppError::Validation {
message: "email must contain @".into(),
});
}
if display_name.is_empty() {
return Err(AppError::Validation {
message: "display_name cannot be blank".into(),
});
}
Ok(Self { email, display_name })
}
}
}
This replaces a lot of annotation-driven validation magic with plain, visible rules.
这一步会把很多依赖注解的校验魔法,换成朴素而可见的规则代码。
Step 5: Keep SQL Visible
第五步:让 SQL 保持可见
#![allow(unused)]
fn main() {
pub struct UserRepository {
pool: sqlx::PgPool,
}
impl UserRepository {
pub async fn find_by_id(&self, id: uuid::Uuid) -> Result<Option<User>, sqlx::Error> {
sqlx::query_as!(
User,
"select id, email, display_name from users where id = $1",
id
)
.fetch_optional(&self.pool)
.await
}
}
}
For migration work, this is often easier to reason about than jumping straight back into another layer of ORM magic.
在迁移工作里,这通常比又一头扎进另一层 ORM 魔法更容易掌控。
Step 6: Move Business Logic into a Service
第六步:把业务逻辑搬进 Service
#![allow(unused)]
fn main() {
pub struct UserService {
repo: UserRepository,
}
impl UserService {
pub async fn get_user(&self, id: uuid::Uuid) -> AppResult<User> {
self.repo
.find_by_id(id)
.await?
.ok_or_else(|| AppError::NotFound {
entity: "user".into(),
id: id.to_string(),
})
}
}
}
This still feels familiar to Java developers, but errors are now explicit and typed.
这一步对 Java 开发者来说依然很熟悉,只不过失败路径现在已经是显式且有类型的了。
Step 7: Wire Handlers and Shared State
第七步:装配 Handler 和共享状态
#![allow(unused)]
fn main() {
#[derive(Clone)]
pub struct AppState {
pub user_service: std::sync::Arc<UserService>,
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<uuid::Uuid>,
) -> AppResult<Json<UserResponse>> {
let user = state.user_service.get_user(id).await?;
Ok(Json(UserResponse {
id: user.id,
email: user.email,
display_name: user.display_name,
}))
}
}
This is the Rust replacement for controller method binding plus dependency injection.
这就是 Rust 对“controller 方法绑定加依赖注入”的替代方案。
Step 8: Build an Error Boundary
第八步:建立错误边界
Use IntoResponse to play the role that @ControllerAdvice often plays in Spring Boot.
可以用 IntoResponse 去承担 Spring Boot 里 @ControllerAdvice 常承担的职责。
Centralized error mapping keeps HTTP behavior stable while the internals change.
即使内部实现正在迁移,集中式错误映射也能让外部 HTTP 行为保持稳定。
Step 9: Test the Contract
第九步:围绕契约做测试
The most valuable migration tests are black-box contract tests:
迁移阶段最值钱的测试,其实是黑盒契约测试:
- same status codes
状态码一致。 - same response shape
响应结构一致。 - same validation behavior
校验行为一致。
Step 10: Roll Out Carefully
第十步:谨慎发布
- mirror traffic if possible
能镜像流量就尽量镜像。 - migrate one tenant or region first
先迁一个租户或一个区域。 - compare latency, memory, and error rate
重点对比延迟、内存和错误率。
Why This Capstone Matters
为什么这个综合项目很重要
This one project forces practice with nearly every major Java-to-Rust transition:
这个项目几乎会把 Java 迁到 Rust 时最关键的转换都练一遍:
- DTO to domain conversion
DTO 到领域对象转换。 - explicit dependency wiring
显式依赖装配。 Resultinstead of exceptions
用Result替代异常流。- handler/service/repository separation
handler、service、repository 分层。 - explicit SQL and HTTP contract preservation
显式 SQL 与 HTTP 契约保持稳定。
Real-World Java-to-Rust References
真实世界的 Java 到 Rust 迁移参考
All links in this section were verified as reachable on March 26, 2026.
本节所有链接都已在 2026 年 3 月 26 日核验可访问。
-
Datadog: static analyzer migration
Datadog:静态分析器迁移案例。 Datadog migrated a production static analyzer from Java to Rust, used feature-parity tests to keep behavior stable, learned enough Rust to map the codebase in about 10 days, completed the overall migration within a month, and reported about 3x faster execution with roughly 10x lower memory use. This is one of the clearest public examples of a disciplined Java-to-Rust migration in a real product. How we migrated our static analyzer from Java to Rust
Datadog 把生产环境里的静态分析器从 Java 迁到 Rust,用特性对齐测试保证行为一致;团队大约 10 天摸清 Rust 的关键概念,1 个月完成整体迁移,并报告约 3 倍执行速度与约 10 倍内存下降。这是公开资料里非常扎实、也非常像真实工程迁移过程的一个案例。 -
CIMB Niaga: banking microservice migration
CIMB Niaga:银行微服务迁移案例。 CIMB Niaga migrated a critical internal authentication microservice from Java to Rust with a phased rollout that ran beside the Java service. Their public numbers are unusually concrete: startup time fell from about 31.9 seconds to under 1 second, CPU use dropped from 3 cores to 0.25 cores, and memory use fell from 3.8 GB to 8 MB. They also explicitly describe the learning curve as steep and mention knowledge sharing plus peer mentoring as part of the migration strategy. Delivering Superior Banking Experiences
CIMB Niaga 把一个关键的内部认证微服务从 Java 迁到 Rust,而且是与原有 Java 服务并行逐步发布。它给出的公开数据很硬:启动时间从约 31.9 秒降到 1 秒以内,CPU 使用从 3 个核心降到 0.25 个核心,内存从 3.8 GB 降到 8 MB。同时他们也明确提到学习曲线比较陡,因此配套采用了知识分享和结对辅导。 -
WebGraph and Software Heritage: large-scale graph processing rewrite
WebGraph 与 Software Heritage:超大规模图处理框架重写。 The WebGraph team rewrote a long-standing Java graph-processing framework in Rust because JVM memory and memory-mapping limits became a bottleneck at Software Heritage scale. Their paper reports about 1.4x to 3.18x speedups on representative workloads and explains how Rust’s type system and compilation model enabled a cleaner, faster implementation for huge immutable datasets. WebGraph: The Next Generation (Is in Rust)
WebGraph 团队之所以把一个存在多年的 Java 图处理框架改写成 Rust,核心原因是 Software Heritage 这种级别的数据规模下,JVM 的内存与内存映射限制已经成了瓶颈。论文里给出的代表性工作负载加速大约在 1.4 倍到 3.18 倍之间,也解释了 Rust 的类型系统和编译模型为什么更适合这类巨大而不可变的数据集。 -
Mike Bursell: a Java developer’s transition notes
Mike Bursell:Java 开发者迁到 Rust 的一手体验。 Mike Bursell describes taking one of his own Java projects and reimplementing it in Rust. The valuable part is the tone: enough of Rust felt familiar to keep going, ownership became understandable with practice, and Cargo plus compiler feedback made the language feel learnable rather than mystical. It is a good first-person account of what the transition feels like after years of Java. Why I switched from Java to Rust
Mike Bursell 讲的是把自己的一个 Java 项目改用 Rust 重写后的真实感受。这个文章有价值的地方在于它很克制:Rust 里有不少地方能让 Java 开发者保持熟悉感,所有权一开始确实拧巴,但通过练习会逐步理解,而 Cargo 加上编译器反馈会让学习过程变得非常具体。 -
Kasun Sameera: practical trade-offs before moving from Java
Kasun Sameera:迁移前必须正视的现实权衡。 Kasun Sameera compares Rust web development with Spring Boot from a Java developer’s perspective. The useful takeaway is the trade-off analysis: Rust web frameworks could outperform the same Spring Boot service, but the initial setup effort, library maturity, and convenience story still favored Java for many business applications. That balance is exactly what engineering teams need to judge honestly before migrating. Before moving to Rust from Java
Kasun Sameera 从 Java 开发者视角把 Rust Web 开发和 Spring Boot 做了一个比较。真正值得看的是他的权衡分析:Rust Web 框架确实可能比同类 Spring Boot 服务更快,但初始化成本、类库成熟度和业务开发便利性,在很多场景里依然还是 Java 更占优。迁移前把这件事想明白,比一腔热血冲过去靠谱得多。
When Java Teams Should Migrate to Rust
什么条件下适合从 Java 迁到 Rust
Rust becomes a strong choice when most of the following are true:
如果下面这些条件大部分都成立,那么 Rust 会是很强的选择:
- predictable latency, low memory usage, or fast startup materially affect user experience or operating cost
稳定延迟、低内存占用或快速启动,会直接影响用户体验或者运行成本。 - the service does parser work, protocol handling, security scanning, gateways, agents, stream processing, or other infrastructure-heavy work where control over performance matters
服务主要承担解析器、协议处理、安全扫描、网关、Agent、流处理这类基础设施型工作,而且性能控制真的很重要。 - the migration target can be isolated behind a clear HTTP, gRPC, queue, or library boundary
迁移目标可以被清晰地隔离在 HTTP、gRPC、消息队列或者库接口边界之后。 - the team is willing to invest in ownership, borrowing, explicit error handling, and stronger test discipline
团队愿意投入时间掌握所有权、借用、显式错误处理,以及更严格的测试纪律。 - success can be measured with concrete metrics instead of general excitement about a new language
迁移成效可以用明确指标来衡量,而不是只靠对新语言的兴奋感。
Java should usually remain the default when most of the following are true:
如果下面这些情况更贴近现实,那么继续留在 Java 往往更合适:
- the main bottleneck is product complexity or delivery throughput rather than runtime performance
主要瓶颈是业务复杂度或者交付速度,而不是运行时性能。 - Spring Boot, JPA, and the existing JVM platform are still the main reason the team ships quickly
Spring Boot、JPA 和现有 JVM 平台,依然是团队快速交付的主要原因。 - the team has no room for training, design reviews, or a slower first migration
团队当前没有余力做培训、设计评审,或者承受第一次迁移带来的节奏变慢。 - the proposal is a full rewrite with weak contract tests and no shadow rollout or rollback plan
方案是整块重写,但契约测试很弱,也没有影子发布和回滚预案。
A practical recommendation for Java teams is to migrate in this order:
比较务实的迁移顺序可以是这样:
- start with one bounded service, parser, background worker, or performance-critical library
先挑一个边界清楚的服务、解析器、后台任务,或者性能敏感的库开始。 - preserve the external contract first and improve internals second
先保证外部契约稳定,再谈内部实现优化。 - run the Java and Rust implementations side by side during validation
验证阶段让 Java 与 Rust 两套实现并行运行。 - measure latency, memory, startup time, and operational simplicity
重点测量延迟、内存、启动时间和运维复杂度。 - expand only after the first migration clearly pays for itself
等第一批迁移确实证明有价值之后,再继续扩大范围。
For most teams, Rust works best as a selective addition to the architecture, not as a blanket replacement for every Java service.
对大多数团队来说,Rust 更适合作为架构里的选择性补强,而不是把所有 Java 服务一股脑全换掉。