Rust for Python Programmers: Complete Training Guide
Rust 面向 Python 程序员:完整训练指南
A comprehensive guide to learning Rust for developers with Python experience. This guide
covers everything from basic syntax to advanced patterns, focusing on the conceptual shifts
required when moving from a dynamically-typed, garbage-collected language to a statically-typed
systems language with compile-time memory safety.
这是一本面向 Python 开发者的 Rust 全面训练指南,内容从基础语法覆盖到高级模式,重点讲清从动态类型、垃圾回收语言迁移到静态类型、具备编译期内存安全保证的系统语言时,思维方式到底要怎么切换。
How to Use This Book
如何使用本书
Self-study format: Work through Part I (ch 1–6) first — these map closely to Python concepts you already know. Part II (ch 7–12) introduces Rust-specific ideas like ownership and traits. Part III (ch 13–16) covers advanced topics and migration.
自学建议:先读第一部分,也就是第 1–6 章,这一段和 Python 已有经验贴得最近。第二部分,也就是第 7–12 章,会进入 Rust 自己那套核心概念,比如所有权和 trait。第三部分,也就是第 13–16 章,则开始处理进阶主题和迁移实践。
Pacing recommendations:
学习节奏建议:
| Chapters | Topic | Suggested Time | Checkpoint |
|---|---|---|---|
| 1–4 第 1–4 章 | Setup, types, control flow 环境准备、类型与控制流 | 1 day 1 天 | You can write a CLI temperature converter in Rust 能够用 Rust 写一个命令行温度转换器。 |
| 5–6 第 5–6 章 | Data structures, enums, pattern matching 数据结构、枚举与模式匹配 | 1–2 days 1–2 天 | You can define an enum with data and match exhaustively on it能够定义携带数据的枚举,并用 match 做穷尽匹配。 |
| 7 第 7 章 | Ownership and borrowing 所有权与借用 | 1–2 days 1–2 天 | You can explain why let s2 = s1 invalidates s1能够讲清 为什么 let s2 = s1 会让 s1 失效。 |
| 8–9 第 8–9 章 | Modules, error handling 模块与错误处理 | 1 day 1 天 | You can create a multi-file project that propagates errors with ?能够建立一个多文件项目,并用 ? 传递错误。 |
| 10–12 第 10–12 章 | Traits, generics, closures, iterators Trait、泛型、闭包与迭代器 | 1–2 days 1–2 天 | You can translate a list comprehension to an iterator chain 能够把列表推导式翻译成迭代器链。 |
| 13 第 13 章 | Concurrency 并发 | 1 day 1 天 | You can write a thread-safe counter with Arc<Mutex<T>>能够用 Arc<Mutex<T>> 写出线程安全计数器。 |
| 14 第 14 章 | Unsafe, PyO3, testing Unsafe、PyO3 与测试 | 1 day 1 天 | You can call a Rust function from Python via PyO3 能够通过 PyO3 从 Python 调用 Rust 函数。 |
| 15–16 第 15–16 章 | Migration, best practices 迁移与最佳实践 | At your own pace 按个人节奏 | Reference material — consult as you write real code 属于参考型内容,写真实项目时可以反复查阅。 |
| 17 第 17 章 | Capstone project 综合项目 | 2–3 days 2–3 天 | Build a complete CLI app tying everything together 完成一个把全部内容串起来的 CLI 应用。 |
How to use the exercises:
练习怎么做:
- Chapters include hands-on exercises in collapsible
<details>blocks with solutions
每章都配有可折叠<details>练习块,并附带答案。 - Always try the exercise before expanding the solution. Struggling with the borrow checker is part of learning — the compiler’s error messages are your teacher
一定要先自己做,再展开答案。 和借用检查器缠斗本来就是学习过程的一部分,编译器报错本身就是老师。 - If you’re stuck for more than 15 minutes, expand the solution, study it, then close it and try again from scratch
如果卡了超过 15 分钟,就先展开答案研究,再关掉答案,从头重做一遍。 - The Rust Playground lets you run code without a local install
Rust Playground 可以在不用本地安装环境的情况下直接运行代码。
Difficulty indicators:
难度标记:
- 🟢 Beginner — Direct translation from Python concepts
🟢 初级:几乎可以从 Python 经验直接迁移过来。 - 🟡 Intermediate — Requires understanding ownership or traits
🟡 中级:开始要求理解所有权或 trait。 - 🔴 Advanced — Lifetimes, async internals, or unsafe code
🔴 高级:涉及生命周期、async 内部机制或 unsafe 代码。
When you hit a wall:
当读到卡壳时:
- Read the compiler error message carefully — Rust’s errors are exceptionally helpful
仔细看编译器报错,Rust 的错误信息通常非常有帮助。 - Re-read the relevant section; concepts like ownership (ch7) often click on the second pass
回头再读相关章节,像所有权这种概念,很多时候第二遍才会真正开窍。 - The Rust standard library docs are excellent — search for any type or method
Rust 标准库文档 质量很高,遇到类型或方法问题可以直接查。 - For deeper async patterns, see the companion Async Rust Training
如果想深入 async 模式,可以配合阅读姊妹教材 Async Rust Training。
Table of Contents
目录总览
Part I — Foundations
第一部分:基础
1. Introduction and Motivation 🟢
1. 引言与动机 🟢
- The Case for Rust for Python Developers
Rust 为什么值得 Python 开发者学习 - Common Python Pain Points That Rust Addresses
Rust 能解决哪些 Python 常见痛点 - When to Choose Rust Over Python
什么时候该选 Rust,而不是 Python
2. Getting Started 🟢
2. 快速开始 🟢
- Installation and Setup
安装与环境配置 - Your First Rust Program
第一个 Rust 程序 - Cargo vs pip/Poetry
Cargo 与 pip / Poetry 的对比
3. Built-in Types and Variables 🟢
3. 内置类型与变量 🟢
- Variables and Mutability
变量与可变性 - Primitive Types Comparison
基本类型对照 - String Types: String vs &str
字符串类型:String 与 &str
4. Control Flow 🟢
4. 控制流 🟢
- Conditional Statements
条件语句 - Loops and Iteration
循环与迭代 - Expression Blocks
表达式代码块 - Functions and Type Signatures
函数与类型签名
5. Data Structures and Collections 🟢
5. 数据结构与集合 🟢
- Tuples, Arrays, Slices
元组、数组与切片 - Structs vs Classes
Struct 与 Class 的差异 - Vec vs list, HashMap vs dict
Vec 对比 list,HashMap 对比 dict
6. Enums and Pattern Matching 🟡
6. 枚举与模式匹配 🟡
- Algebraic Data Types vs Union Types
代数数据类型与联合类型 - Exhaustive Pattern Matching
穷尽模式匹配 - Option for None Safety
用 Option 处理 None 安全
Part II — Core Concepts
第二部分:核心概念
7. Ownership and Borrowing 🟡
7. 所有权与借用 🟡
- Understanding Ownership
理解所有权 - Move Semantics vs Reference Counting
移动语义与引用计数 - Borrowing and Lifetimes
借用与生命周期 - Smart Pointers
智能指针
8. Crates and Modules 🟢
8. crate 与模块 🟢
- Rust Modules vs Python Packages
Rust 模块与 Python 包 - Crates vs PyPI Packages
crate 与 PyPI 包
9. Error Handling 🟡
9. 错误处理 🟡
- Exceptions vs Result
异常与 Result 的差异 - The ? Operator
?运算符 - Custom Error Types with thiserror
用 thiserror 定义自定义错误类型
10. Traits and Generics 🟡
10. Trait 与泛型 🟡
- Traits vs Duck Typing
Trait 与鸭子类型对比 - Protocols (PEP 544) vs Traits
Protocol 与 Trait 对比 - Generic Constraints
泛型约束
11. From and Into Traits 🟡
11. From 与 Into Trait 🟡
- Type Conversions in Rust
Rust 中的类型转换 - From, Into, TryFrom
From、Into、TryFrom - String Conversion Patterns
字符串转换模式
12. Closures and Iterators 🟡
12. 闭包与迭代器 🟡
- Closures vs Lambdas
闭包与 lambda - Iterators vs Generators
迭代器与生成器 - Macros: Code That Writes Code
宏:生成代码的代码
Part III — Advanced Topics & Migration
第三部分:进阶主题与迁移
13. Concurrency 🔴
13. 并发 🔴
- No GIL: True Parallelism
没有 GIL:真正的并行 - Thread Safety: Type System Guarantees
线程安全:类型系统保证 - async/await Comparison
async/await 对比
14. Unsafe Rust, FFI, and Testing 🔴
14. Unsafe Rust、FFI 与测试 🔴
- When and Why to Use Unsafe
何时以及为何使用 Unsafe - PyO3: Rust Extensions for Python
PyO3:面向 Python 的 Rust 扩展 - Unit Tests vs pytest
单元测试与 pytest 对比
15. Migration Patterns 🟡
15. 迁移模式 🟡
- Common Python Patterns in Rust
Python 常见模式在 Rust 中的写法 - Essential Crates for Python Developers
Python 开发者必备 crate - Incremental Adoption Strategy
渐进式引入策略
16. Best Practices 🟡
16. 最佳实践 🟡
- Idiomatic Rust for Python Developers
Python 开发者该掌握的 Rust 惯用法 - Common Pitfalls and Solutions
常见陷阱与解决方案 - Python→Rust Rosetta Stone
Python → Rust 对照手册 - Learning Path and Resources
学习路径与资源
Part IV — Capstone
第四部分:综合项目
17. Capstone Project: CLI Task Manager 🔴
17. 综合项目:命令行任务管理器 🔴
- The Project:
rustdo
项目介绍:rustdo - Data Model, Storage, Commands, Business Logic
数据模型、存储、命令与业务逻辑 - Tests and Stretch Goals
测试与延伸目标
Introduction and Motivation §§ZH§§ 引言与动机
Speaker Intro and General Approach
讲者背景与整体方式
- Speaker intro
讲者背景
微软 SCHIE 团队的 Principal Firmware Architect,长期做安全、系统编程、固件、操作系统、虚拟化和 CPU / 平台架构相关工作。 - Industry veteran with expertise in security, systems programming, CPU and platform architecture, and C++ systems
在安全、系统编程、底层平台与 C++ 系统方向积累很深。 - Started programming in Rust in 2017 and has loved it ever since
从 2017 年开始使用 Rust,并长期投入其中。 - This course is intended to be interactive
这门课希望尽量保持互动式学习。 - Assumption: you already know Python and its ecosystem
默认前提是:已经熟悉 Python 及其生态。 - Examples deliberately map Python concepts to Rust equivalents
所有示例都会尽量把 Python 概念映射到 Rust 的对应物。 - Clarifying questions are encouraged at any time
遇到不清楚的地方,随时打断提问完全没问题。
The Case for Rust for Python Developers
为什么 Python 开发者值得学 Rust
What you’ll learn: Why Python developers are adopting Rust, where the real-world performance gains come from, when Rust is the right choice and when Python remains the better tool, and the philosophical differences between the two languages.
本章将学习: 为什么越来越多 Python 开发者开始接触 Rust,真实世界里的性能收益来自哪里,什么时候该选 Rust、什么时候继续用 Python 更合适,以及这两门语言在设计哲学上的差异。Difficulty: 🟢 Beginner
难度: 🟢 入门
Performance: From Minutes to Milliseconds
性能:从分钟级到毫秒级
Python is famous for developer speed, not CPU efficiency. For CPU-bound tasks, Rust often lands orders of magnitude faster while still keeping relatively high-level syntax.
Python 出名的是开发速度,不是 CPU 执行效率。对 CPU 密集任务来说,Rust 往往能快出数量级,同时语法层面又没有低到让人完全失去抽象。
# Python — ~2 seconds for 10 million calls
import time
def fibonacci(n: int) -> int:
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
start = time.perf_counter()
results = [fibonacci(n % 30) for n in range(10_000_000)]
elapsed = time.perf_counter() - start
print(f"Elapsed: {elapsed:.2f}s") # ~2s on typical hardware
// Rust — ~0.07 seconds for the same 10 million calls
use std::time::Instant;
fn fibonacci(n: u64) -> u64 {
if n <= 1 {
return n;
}
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let temp = b;
b = a + b;
a = temp;
}
b
}
fn main() {
let start = Instant::now();
let results: Vec<u64> = (0..10_000_000).map(|n| fibonacci(n % 30)).collect();
println!("Elapsed: {:.2?}", start.elapsed()); // ~0.07s
}
Note: Rust should be run in release mode (
cargo run --release) for a fair performance comparison.
说明: 做这种性能对比时,Rust 一定要用cargo run --release跑,否则数字会被 debug 构建拖得很难看。
Why the difference? Python dispatches arithmetic through runtime object machinery, dictionary lookups, heap-allocated integers, and dynamic type checks. Rust compiles the same logic down to simple machine instructions.
为什么差距会这么大? 因为 Python 的算术操作要经过运行时对象系统、字典查找、堆对象拆装以及动态类型检查;Rust 则会把同样的逻辑直接编译成很朴素的机器指令。
Memory Safety Without a Garbage Collector
没有垃圾回收器的内存安全
Python’s reference-counting GC is convenient, but it also brings circular references, __del__ timing uncertainty, and memory fragmentation issues. Rust chooses a different route: ownership and compile-time guarantees.
Python 的引用计数加垃圾回收确实省心,但它也会带来循环引用、__del__ 时机不稳定、内存碎片等问题。Rust 走的是另一条路:所有权加编译期约束。
# Python — circular reference
class Node:
def __init__(self, value):
self.value = value
self.parent = None
self.children = []
def add_child(self, child):
self.children.append(child)
child.parent = self
root = Node("root")
child = Node("child")
root.add_child(child)
// Rust — ownership prevents cycles by default
struct Node {
value: String,
children: Vec<Node>,
}
impl Node {
fn new(value: &str) -> Self {
Node {
value: value.to_string(),
children: Vec::new(),
}
}
fn add_child(&mut self, child: Node) {
self.children.push(child);
}
}
fn main() {
let mut root = Node::new("root");
let child = Node::new("child");
root.add_child(child);
}
Key insight: Rust defaults to tree-like ownership. If a graph or shared back-reference is truly needed, the code must opt in explicitly with
Rc、RefCell、Weakor similar tools.
关键理解: Rust 默认偏向树形所有权结构。如果真的需要图结构、共享回指之类复杂关系,就必须显式引入Rc、RefCell、Weak这些工具,把复杂度摆到台面上来。
Common Python Pain Points That Rust Addresses
Rust 正面解决的几类 Python 常见痛点
1. Runtime Type Errors
1. 运行时类型错误
def process_user(user_id: int, name: str) -> dict:
return {"id": user_id, "name": name.upper()}
process_user("not-a-number", 42)
process_user(None, "Alice")
process_user(1, "Alice", extra="oops")
#![allow(unused)]
fn main() {
fn process_user(user_id: i64, name: &str) -> User {
User {
id: user_id,
name: name.to_uppercase(),
}
}
// process_user("not-a-number", 42); // ❌ Compile error
// process_user(None, "Alice"); // ❌ Compile error
#[derive(Deserialize)]
struct UserInput {
id: i64,
name: String,
}
let input: UserInput = serde_json::from_str(json_str)?;
process_user(input.id, &input.name);
}
Rust’s approach is to make bad combinations literally unrepresentable in ordinary code. The compiler catches the mismatch before the process even starts.
Rust 的思路是:让错误组合在普通代码里根本写不通。类型一旦对不上,程序甚至连启动机会都没有。
2. None: The Billion Dollar Mistake
2. None:价值连城的历史大坑
def find_user(user_id: int) -> dict | None:
users = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
return users.get(user_id)
user = find_user(999)
print(user["name"]) # 💥
#![allow(unused)]
fn main() {
fn find_user(user_id: i64) -> Option<User> {
let users = HashMap::from([
(1, User { name: "Alice".into() }),
(2, User { name: "Bob".into() }),
]);
users.get(&user_id).cloned()
}
match find_user(999) {
Some(user) => println!("{}", user.name),
None => println!("User not found"),
}
let name = find_user(999)
.map(|u| u.name)
.unwrap_or_else(|| "Unknown".to_string());
}
Rust does allow absence, but only through Option<T>, which forces the possibility into the type. That small shift removes an enormous class of “surprise None” failures.
Rust 当然允许“值可能不存在”,但必须通过 Option<T> 把这种可能性写进类型系统。这个小改动,实际上直接消掉了非常大一类“突然冒出 None” 的运行时事故。
3. The GIL: Python’s Concurrency Ceiling
3. GIL:Python 并发的天花板
import threading
import time
def cpu_work(n):
total = 0
for i in range(n):
total += i * i
return total
start = time.perf_counter()
threads = [threading.Thread(target=cpu_work, args=(10_000_000,)) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
elapsed = time.perf_counter() - start
print(f"4 threads: {elapsed:.2f}s")
use std::thread;
fn cpu_work(n: u64) -> u64 {
(0..n).map(|i| i * i).sum()
}
fn main() {
let start = std::time::Instant::now();
let handles: Vec<_> = (0..4)
.map(|_| thread::spawn(|| cpu_work(10_000_000)))
.collect();
let results: Vec<u64> = handles.into_iter()
.map(|h| h.join().unwrap())
.collect();
println!("4 threads: {:.2?}", start.elapsed());
}
With Rayon: parallelism can become even simpler with
par_iter(), which often feels like the “why doesn’t Python just let me do this?” moment for many learners.
如果用 Rayon:并行会变得更简单,很多人第一次看到par_iter()时,都会有一种“这事在 Python 里怎么就这么费劲”的直观冲击。
4. Deployment and Distribution Pain
4. 部署与分发的痛点
Python deployment often means juggling Python versions, virtual environments, wheels, system libraries, and runtime startup cost. Rust usually means shipping one binary.
Python 部署经常伴随版本、虚拟环境、wheel、系统库和运行时启动成本这些问题;Rust 的常见答案则是:交付一个二进制文件。
# Python deployment checklist:
# 1. Which Python version?
# 2. Virtual environment?
# 3. C extensions and wheels?
# 4. System dependencies?
# 5. Large Docker images?
# 6. Import-heavy startup time?
#![allow(unused)]
fn main() {
// Rust deployment:
// cargo build --release -> one binary
// copy it anywhere, no runtime required
}
When to Choose Rust Over Python
什么时候该选 Rust
Choose Rust When:
更适合选 Rust 的场景
- Performance is critical
性能是刚性要求 - Correctness matters deeply
正确性要求非常高 - Deployment simplicity matters
部署链路越简单越好 - Low-level control is required
需要更底层的控制能力 - True parallelism is valuable
真正的 CPU 并行有价值 - Memory efficiency affects cost
内存效率直接影响成本 - Latency predictability matters
对延迟稳定性有要求
Stay with Python When:
继续留在 Python 更划算的场景
- Rapid prototyping dominates
原型速度最重要 - ML / AI ecosystem is the core value
核心价值在 ML / AI 生态 - The code is mostly glue and orchestration
代码主要是胶水与编排 - Team learning cost outweighs the gain
学习成本明显高于收益 - Time to market beats runtime speed
上线速度比执行速度更重要 - Interactive workflows dominate
Jupyter、REPL、交互分析是主工作流
Consider Both (Hybrid with PyO3):
两者结合的场景(PyO3 混合方案)
- Compute-heavy code in Rust
计算热点用 Rust - Business orchestration in Python
业务编排继续留给 Python - Incremental migration of hotspots
从热点模块开始渐进迁移 - Keep Python’s ecosystem, add Rust’s performance
保留 Python 生态,同时引入 Rust 性能
Real-World Impact: Why Companies Choose Rust
真实世界里的价值:为什么公司会选 Rust
Dropbox: Storage Infrastructure
Dropbox:存储基础设施
- Python 版本 CPU 占用和内存开销更高
原先 Python 方案 CPU 和内存压力都比较大 - Rust 版本带来明显性能提升和内存下降
Rust 版本显著改善性能和内存使用
Discord: Voice / Video Backend
Discord:语音视频后端
- Earlier stacks suffered from pause-related latency problems
早期方案在暂停与抖动上有明显问题 - Rust gave more stable low-latency behavior
Rust 带来了更稳定的低延迟表现
Cloudflare: Edge Workers
Cloudflare:边缘计算
- Rust works well with WebAssembly and predictable edge execution
Rust 很适合 WebAssembly 和边缘执行场景
Pydantic V2
Pydantic V2
- The public Python API stayed familiar
对外 Python API 基本保持不变 - The Rust core delivered dramatic validation speedups
底层 Rust 核心把校验性能拉上去了
Why This Matters for Python Developers:
这对 Python 开发者意味着什么
- The skills are complementary
1. 两门语言的能力是互补关系 - PyO3 makes bridging practical
2. PyO3 让桥接落地变得现实 - Learning Rust clarifies where Python pays overhead
3. 学 Rust 会反过来帮助理解 Python 的性能成本到底花在哪 - Systems knowledge broadens career options
4. 系统编程能力会显著扩展技术边界 - Better performance often means lower infrastructure cost
5. 性能提升通常还会带来实打实的基础设施成本下降
Language Philosophy Comparison
语言哲学对照
Python Philosophy
Python 的哲学
- Readability counts
可读性优先 - Batteries included
标准库与生态都很完整 - Duck typing
鸭子类型 - Developer velocity first
优先优化开发速度 - Dynamic everything
动态性很强
Rust Philosophy
Rust 的哲学
- Performance without sacrifice
不牺牲性能 - Correctness first
正确性优先 - Explicit over implicit
显式优于隐式 - Ownership everywhere
资源由所有权统一管理 - Fearless concurrency
并发安全尽量前移到编译期
graph LR
subgraph PY["🐍 Python"]
direction TB
PY_CODE["Your Code<br/>业务代码"] --> PY_INTERP["Interpreter<br/>解释器"]
PY_INTERP --> PY_GC["Garbage Collector<br/>垃圾回收"]
PY_GC --> PY_GIL["GIL<br/>线程并行受限"]
PY_GIL --> PY_OS["OS / Hardware"]
end
subgraph RS["🦀 Rust"]
direction TB
RS_CODE["Your Code<br/>业务代码"] --> RS_NONE["No runtime overhead<br/>尽量无额外运行时"]
RS_NONE --> RS_OWN["Ownership<br/>编译期所有权约束"]
RS_OWN --> RS_THR["Native threads<br/>原生线程并行"]
RS_THR --> RS_OS["OS / Hardware"]
end
style PY_INTERP fill:#fff3e0,color:#000,stroke:#e65100
style PY_GC fill:#fff3e0,color:#000,stroke:#e65100
style PY_GIL fill:#ffcdd2,color:#000,stroke:#c62828
style RS_NONE fill:#c8e6c9,color:#000,stroke:#2e7d32
style RS_OWN fill:#c8e6c9,color:#000,stroke:#2e7d32
style RS_THR fill:#c8e6c9,color:#000,stroke:#2e7d32
Quick Reference: Rust vs Python
速查表:Rust vs Python
| Concept 概念 | Python | Rust | Key Difference 核心差异 |
|---|---|---|---|
| Typing | Dynamic | Static | Errors move earlier 错误更早暴露 |
| Memory | GC + refcount | Ownership | Deterministic cleanup 确定性清理 |
| None/null | None anywhere | Option<T> | Absence is explicit 空值显式建模 |
| Error handling | raise / try / except | Result<T, E> | Explicit control flow 控制流更显式 |
| Mutability | Everything mutable | Immutable by default | Mutation is opt-in 修改需要显式声明 |
| Speed | Interpreted | Compiled | Much faster execution 执行速度通常快很多 |
| Concurrency | GIL-limited | No GIL | True parallelism 真并行 |
| Dependencies | pip / Poetry | Cargo | Unified tooling 工具链更统一 |
| Build system | Multiple tools | Cargo | Single main workflow 主流程更统一 |
| Packaging | pyproject.toml | Cargo.toml | Both declarative |
| REPL | Native REPL | No main REPL | Compile-first workflow |
| Type hints | Optional | Enforced | Types are executable constraints 类型是强制约束 |
Exercises
练习
🏋️ Exercise: Mental Model Check
🏋️ 练习:心智模型检查
Challenge: For each Python snippet below, describe what Rust would require differently. No need to write full code; just explain the constraint.
挑战:针对下面每段 Python 代码,描述 Rust 会提出什么不同要求。这里不用写完整代码,只要说清楚约束就行。
x = [1, 2, 3]; y = x; x.append(4)
1.x = [1, 2, 3]; y = x; x.append(4)data = None; print(data.upper())
2.data = None; print(data.upper())import threading; shared = []; threading.Thread(target=shared.append, args=(1,)).start()
3.import threading; shared = []; threading.Thread(target=shared.append, args=(1,)).start()
🔑 Solution
🔑 参考答案
- Ownership move:
let y = x;would move ownership, so trying to mutatexafterward would fail to compile unless the design uses borrowing or cloning.
1. 所有权移动:如果写成let y = x;,所有权会转移给y,之后再改x会直接编译失败,除非改成借用或显式克隆。 - No implicit nulls:
datawould have to beOption<String>and theNonecase must be handled before calling string methods.
2. 没有隐式空值:data必须被写成Option<String>,并且在调用字符串方法前先把None分支处理掉。 - Thread-safety markers: shared mutable state across threads must be wrapped in something like
Arc<Mutex<Vec<i32>>>, and the compiler checks the access pattern.
3. 线程安全约束:跨线程共享可变状态通常要包进Arc<Mutex<Vec<i32>>>之类结构,而且访问方式会被编译器严格检查。
Key takeaway: Rust shifts a lot of “this might explode later” uncertainty into “this does not compile until the ownership and safety story is coherent.”
核心收获:Rust 会把大量“以后可能炸”的不确定性,前移成“在所有权和安全逻辑说通之前根本编不过”。
Getting Started §§ZH§§ 快速开始
Installation and Setup
安装与环境准备
What you’ll learn: How to install Rust and its toolchain, how Cargo compares with pip and Poetry, how to set up an IDE, how the first
Hello, world!program differs from Python, and which core Rust keywords map to familiar Python ideas.
本章将学习: 如何安装 Rust 及其工具链,Cargo 和 pip、Poetry 的对应关系,如何配置 IDE,第一段Hello, world!程序和 Python 有什么差异,以及哪些 Rust 关键字可以映射到熟悉的 Python 概念。Difficulty: 🟢 Beginner
难度: 🟢 入门
Installing Rust
安装 Rust
# Install Rust via rustup (Linux/macOS/WSL)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verify installation
rustc --version
cargo --version
# Update Rust
rustup update
Rust installation is intentionally simple: rustup manages the compiler, standard library, Cargo, and toolchain updates in one place.
Rust 的安装流程相对统一,rustup 会同时管理编译器、标准库、Cargo 和后续工具链更新,省掉了不少来回折腾环境的麻烦。
Rust Tools vs Python Tools
Rust 工具链与 Python 工具链对照
| Purpose 用途 | Python | Rust |
|---|---|---|
| Language runtime 语言运行核心 | python interpreter | rustc compiler |
| Package manager 包管理 | pip / poetry / uv | cargo |
| Project config 项目配置 | pyproject.toml | Cargo.toml |
| Lock file 锁文件 | poetry.lock / requirements.txt | Cargo.lock |
| Virtual env 虚拟环境 | venv / conda | Not needed 通常不需要 |
| Formatter 格式化 | black / ruff format | rustfmt / cargo fmt |
| Linter 静态检查 | ruff / flake8 / pylint | clippy / cargo clippy |
| Type checker 类型检查 | mypy / pyright | Built into compiler 编译器内建 |
| Test runner 测试 | pytest | cargo test |
| Docs 文档 | sphinx / mkdocs | cargo doc |
| REPL 交互式环境 | python / ipython | None 没有内建 REPL |
IDE Setup
IDE 配置
VS Code (recommended):
VS Code(推荐入门使用):
Extensions to install:
- rust-analyzer
- Even Better TOML
- CodeLLDB
# Python equivalent mapping:
# rust-analyzer ≈ Pylance
# cargo clippy ≈ ruff
建议安装的扩展:- `rust-analyzer`:核心中的核心,负责补全、跳转、类型信息等 IDE 能力
- `Even Better TOML`:让 `Cargo.toml` 这类文件的编辑体验正常起来
- `CodeLLDB`:调试支持
如果非要类比:
- `rust-analyzer` 的定位有点像 Python 世界里的 Pylance
- `cargo clippy` 的角色有点像 `ruff`,但它还会兼顾更多正确性检查
Your First Rust Program
第一段 Rust 程序
Python Hello World
Python 版 Hello World
# hello.py — just run it
print("Hello, World!")
# Run:
# python hello.py
Rust Hello World
Rust 版 Hello World
// src/main.rs — must be compiled first
fn main() {
println!("Hello, World!"); // println! is a macro
}
// Build and run:
// cargo run
Key Differences for Python Developers
Python 开发者最先要适应的差别
Python: Rust:
───────── ─────
- No main() needed - fn main() is the entry point
- Indentation = blocks - Curly braces {} = blocks
- print() is a function - println!() is a macro
- No semicolons - Semicolons end statements
- No type declarations - Types inferred but always known
- Interpreted (run directly) - Compiled first
- Errors at runtime - Most errors at compile time
最直观的区别有这几条:- Python 不要求 `main()`,Rust 由 `fn main()` 作为入口
- Python 用缩进表示代码块,Rust 用花括号
- `print()` 在 Python 里是函数,`println!()` 在 Rust 里是宏,后面的 `!` 很关键
- Python 基本看不到分号,Rust 里分号会结束语句
- Rust 常常能做类型推导,但类型始终是明确存在的
- Python 通常边解释边运行,Rust 先编译再执行
- 很多 Python 运行时问题,会被 Rust 提前到编译期
Creating Your First Project
创建第一个项目
# Python # Rust
mkdir myproject cargo new myproject
cd myproject cd myproject
python -m venv .venv # No virtual env needed
source .venv/bin/activate # No activation needed
# Create files manually # src/main.rs already created
# Python project structure: Rust project structure:
# myproject/ myproject/
# ├── pyproject.toml ├── Cargo.toml
# ├── src/ ├── src/
# │ └── myproject/ │ └── main.rs
# │ ├── __init__.py └── (no __init__.py needed)
# │ └── main.py
# └── tests/
# └── test_main.py
graph TD
subgraph Python ["Python Project<br/>Python 项目"]
PP["pyproject.toml"] --- PS["src/"]
PS --- PM["myproject/"]
PM --- PI["__init__.py"]
PM --- PMN["main.py"]
PP --- PT["tests/"]
end
subgraph Rust ["Rust Project<br/>Rust 项目"]
RC["Cargo.toml"] --- RS["src/"]
RS --- RM["main.rs"]
RC --- RTG["target/<br/>自动生成"]
end
style Python fill:#ffeeba
style Rust fill:#d4edda
Key difference: Rust project layout is usually simpler. There is no
__init__.py, no virtual environment activation step, and no split between several competing packaging tools.
关键差异: Rust 项目结构通常更简单,没有__init__.py,没有虚拟环境激活这一步,也没有多套打包工具并存带来的混乱。
Cargo vs pip/Poetry
Cargo 与 pip / Poetry 的对应关系
Project Configuration
项目配置
# Python — pyproject.toml
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"requests>=2.28",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
# Rust — Cargo.toml
[package]
name = "myproject"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = "0.12"
serde = { version = "1.0", features = ["derive"] }
[dev-dependencies]
# Test dependencies — only compiled for `cargo test`
Common Cargo Commands
常用 Cargo 命令
# Python equivalent # Rust
pip install requests cargo add reqwest
pip install -r requirements.txt cargo build
pip install -e . cargo build
python -m pytest cargo test
python -m mypy . # Built into compiler
ruff check . cargo clippy
ruff format . cargo fmt
python main.py cargo run
python -c "..." # No equivalent
# Rust-specific:
cargo new myproject
cargo build --release
cargo doc --open
cargo update
Cargo combines package management, dependency resolution, building, testing, formatting entry points, and documentation into one consistent interface. That unified experience is one of Rust’s strongest ergonomic advantages.
Cargo 把包管理、依赖解析、构建、测试、格式化入口和文档生成统一到了一个接口里。这种一致性,本身就是 Rust 工具链很大的优势。
Essential Rust Keywords for Python Developers
Python 开发者需要先认识的 Rust 关键字
Variable and Mutability Keywords
变量与可变性
#![allow(unused)]
fn main() {
let name = "Alice";
// name = "Bob"; // ❌ immutable by default
let mut count = 0;
count += 1;
const MAX_SIZE: usize = 1024;
static VERSION: &str = "1.0";
}
let declares a variable, but it is immutable by default. mut is an explicit opt-in to changeability. That single design choice already separates Rust sharply from Python’s default-all-mutable feel.let 用来声明变量,但默认不可变。mut 则是显式打开可变性。这一个设计决定,就已经让 Rust 和 Python “默认都能改”的感觉拉开了很大距离。
Ownership and Borrowing Keywords
所有权与借用
#![allow(unused)]
fn main() {
fn print_name(name: &str) { }
fn append(list: &mut Vec<i32>) { }
let s1 = String::from("hello");
let s2 = s1;
// println!("{}", s1); // ❌ value moved
}
These concepts have no direct Python equivalent. & means borrowing, &mut means mutable borrowing, and assignment may move ownership rather than copy a reference.
这几样东西在 Python 里都没有完全对应物。& 表示借用,&mut 表示可变借用,而赋值在 Rust 里可能发生的是所有权转移,不只是“多了一个引用名”。
Type Definition Keywords
类型定义相关关键字
#![allow(unused)]
fn main() {
struct Point {
x: f64,
y: f64,
}
enum Shape {
Circle(f64),
Rectangle(f64, f64),
}
impl Point {
fn distance(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
trait Drawable {
fn draw(&self);
}
type UserId = i64;
}
Control Flow Keywords
控制流关键字
#![allow(unused)]
fn main() {
match value {
1 => println!("one"),
2 | 3 => println!("two or three"),
_ => println!("other"),
}
if let Some(x) = optional_value {
println!("{}", x);
}
loop {
break;
}
for item in collection.iter() {
println!("{}", item);
}
while let Some(item) = stack.pop() {
process(item);
}
}
match and if let deserve special attention. They make destructuring and branch selection much more central than in everyday Python code.match 和 if let 尤其值得重点适应。它们让模式匹配和结构拆解在日常代码里出现得远比 Python 更频繁。
Visibility Keywords
可见性关键字
#![allow(unused)]
fn main() {
pub fn greet() { }
pub(crate) fn internal() { }
fn private_helper() { }
}
Python’s privacy is mostly by convention. Rust’s privacy is enforced by the compiler, and that tends to make module boundaries far more explicit.
Python 的“私有”大多靠约定,Rust 的可见性则由编译器强制执行,因此模块边界通常会清楚得多。
Exercises
练习
🏋️ Exercise: First Rust Program
🏋️ 练习:第一段 Rust 程序
Challenge: Create a new Rust project and write a program that declares a name, counts from 1 to 5, prints greeting messages, and then reports whether the final count is even or odd with match.
挑战:新建一个 Rust 项目,声明一个名字变量,用循环把计数加到 5,每次打印问候语,最后再用 match 判断计数结果是奇数还是偶数。
🔑 Solution
🔑 参考答案
cargo new hello_rust && cd hello_rust
fn main() {
let name = "Pythonista";
let mut count = 0u32;
for _ in 1..=5 {
count += 1;
println!("Hello, {name}! (count: {count})");
}
let parity = match count % 2 {
0 => "even",
_ => "odd",
};
println!("Final count {count} is {parity}");
}
Key takeaways:
核心收获:
letis immutable by default.let默认不可变。1..=5is an inclusive range, similar to Python’srange(1, 6).1..=5是包含末尾的区间,类似 Python 的range(1, 6)。matchis an expression and can return a value.match是表达式,可以直接产出值。- There is no
if __name__ == "__main__"ceremony;fn main()is enough.
这里没有if __name__ == "__main__"这层样板,写fn main()就够了。
Built-in Types and Variables §§ZH§§ 内置类型与变量
Variables and Mutability
变量与可变性
What you’ll learn: Immutable-by-default bindings, explicit
mut, Rust’s primitive numeric types versus Python’s arbitrary-precisionint, the difference betweenStringand&str, formatting, and when type annotations are required.
本章将学习: 默认不可变绑定、显式mut、Rust 原生数值类型和 Python 任意精度int的差异、String与&str的区别、格式化输出,以及什么时候必须写类型标注。Difficulty: 🟢 Beginner
难度: 🟢 入门
Python Variable Declaration
Python 的变量声明
# Python — everything is mutable, dynamically typed
count = 0 # Mutable, type inferred as int
count = 5 # ✅ Works
count = "hello" # ✅ Works — type can change! (dynamic typing)
# "Constants" are just convention:
MAX_SIZE = 1024 # Nothing prevents MAX_SIZE = 999 later
Rust Variable Declaration
Rust 的变量声明
#![allow(unused)]
fn main() {
// Rust — immutable by default, statically typed
let count = 0; // Immutable, type inferred as i32
// count = 5; // ❌ Compile error: cannot assign twice to immutable variable
// count = "hello"; // ❌ Compile error: expected integer, found &str
let mut count = 0; // Explicitly mutable
count = 5; // ✅ Works
// count = "hello"; // ❌ Still can't change type
const MAX_SIZE: usize = 1024; // True constant — enforced by compiler
}
Key Mental Shift for Python Developers
Python 开发者最需要切换的心智
#![allow(unused)]
fn main() {
// Python: variables are labels that point to objects
// Rust: variables are named storage locations that OWN their values
// Variable shadowing — unique to Rust, very useful
let input = "42"; // &str
let input = input.parse::<i32>().unwrap(); // Now it's i32 — new variable, same name
let input = input * 2; // Now it's 84 — another new variable
// In Python, you'd just reassign and lose the old type:
input = "42"
input = int(input)
But in Rust, each `let` creates a genuinely new binding. The old one is gone.
}
Rust variable shadowing looks a bit like reassignment at first glance, but it is actually a new binding each time. That difference becomes very useful when transforming values step by step without reaching for awkward variable names.
Rust 的 shadowing 表面看像重新赋值,其实每次 let 都是在创建一个新绑定。这一点在逐步转换数据时特别顺手,不用为了中间态硬造一堆别扭变量名。
Practical Example: Counter
实用例子:计数器
# Python version
class Counter:
def __init__(self):
self.value = 0
def increment(self):
self.value += 1
def get_value(self):
return self.value
c = Counter()
c.increment()
print(c.get_value()) # 1
// Rust version
struct Counter {
value: i64,
}
impl Counter {
fn new() -> Self {
Counter { value: 0 }
}
fn increment(&mut self) { // &mut self = I will modify this
self.value += 1;
}
fn get_value(&self) -> i64 { // &self = I only read this
self.value
}
}
fn main() {
let mut c = Counter::new(); // Must be `mut` to call increment()
c.increment();
println!("{}", c.get_value()); // 1
}
Key difference: In Rust,
&mut selftells both the reader and the compiler that a method mutates state. In Python, a method may mutate almost anything, and whether it does so is only visible after reading the implementation.
关键差异: Rust 里&mut self会明确告诉读代码的人和编译器“这个方法会修改状态”。Python 里方法能改什么,很多时候只能靠看具体实现才知道。
Primitive Types Comparison
原始类型对照
flowchart LR
subgraph Python ["Python Types<br/>Python 类型"]
PI["int<br/>(arbitrary precision)<br/>任意精度"]
PF["float<br/>(64-bit only)<br/>固定 64 位"]
PB["bool"]
PS["str<br/>(Unicode)"]
end
subgraph Rust ["Rust Types<br/>Rust 类型"]
RI["i8 / i16 / i32 / i64 / i128<br/>u8 / u16 / u32 / u64 / u128"]
RF["f32 / f64"]
RB["bool"]
RS["String / &str"]
end
PI -->|"fixed-size<br/>固定尺寸"| RI
PF -->|"choose precision<br/>显式选择精度"| RF
PB -->|"same<br/>概念一致"| RB
PS -->|"owned vs borrowed<br/>所有权与借用"| RS
style Python fill:#ffeeba
style Rust fill:#d4edda
Numeric Types
数值类型
| Python | Rust | Notes 说明 |
|---|---|---|
int (arbitrary precision) | i8、i16、i32、i64、i128、isize | Fixed-size signed integers 固定大小的有符号整数 |
int (no unsigned variant) | u8、u16、u32、u64、u128、usize | Explicit unsigned integers 显式无符号整数 |
float (64-bit IEEE 754) | f32、f64 | Rust can choose precision Rust 可以显式选精度 |
bool | bool | Same concept 概念基本一致 |
complex | No built-in 标准库无内建 | Use num crate if needed需要时用 num crate |
# Python — one integer type, arbitrary precision
x = 42
big = 2 ** 1000
y = 3.14
#![allow(unused)]
fn main() {
// Rust — explicit sizes, overflow is a compile/runtime error
let x: i32 = 42;
let y: f64 = 3.14;
let big: i128 = 2_i128.pow(100); // No arbitrary precision
// For arbitrary precision: use the `num-bigint` crate
let million = 1_000_000; // Same underscore syntax as Python
let a = 42u8;
let b = 3.14f32;
}
Size Types (Important!)
尺寸相关类型(非常重要)
#![allow(unused)]
fn main() {
// usize and isize — pointer-sized integers, used for indexing
let length: usize = vec![1, 2, 3].len();
let index: usize = 0;
let i: i32 = 5;
// let item = vec[i]; // ❌ Error: expected usize, found i32
let item = vec[i as usize];
}
Python lets len() and indexing both live in plain int. Rust separates “generic integer math” from “memory-size / indexing” integers much more explicitly.
Python 里 len() 和下标都统一落在 int 上。Rust 则把“普通整数运算”和“跟内存尺寸、索引有关的整数”区分得更明确,所以 usize 和 isize 这两个类型一定得尽早看顺眼。
Type Inference
类型推导
#![allow(unused)]
fn main() {
let x = 42; // i32 by default
let y = 3.14; // f64 by default
let s = "hello"; // &str
let v = vec![1, 2]; // Vec<i32>
let x: i64 = 42;
let y: f32 = 3.14;
let x = 42;
// x = "hello"; // ❌ Type can never change
}
Rust does infer types aggressively, but once inferred the type is fixed for that binding. Inference saves keystrokes; it does not make the language dynamic.
Rust 的类型推导很积极,但推导完以后类型就固定了。推导只是省手,不是把语言变成动态类型。
String Types: String vs &str
字符串类型:String 与 &str
For Python developers, this is usually the first really confusing string-related concept: Python has one dominant string type, Rust has two primary string forms used side by side.
对 Python 开发者来说,这往往是最早让人发懵的字符串概念。Python 基本只有一个主力字符串类型,Rust 则并排使用两种核心字符串形态。
Python String Handling
Python 的字符串处理
# Python — one string type, immutable, reference counted
name = "Alice"
greeting = f"Hello, {name}!"
chars = list(name)
upper = name.upper()
Rust String Types
Rust 的两种字符串类型
#![allow(unused)]
fn main() {
// Rust has TWO string types:
// 1. &str (string slice) — borrowed, immutable
let name: &str = "Alice";
// 2. String (owned string) — heap-allocated, growable, owned
let mut greeting = String::from("Hello, ");
greeting.push_str(name);
greeting.push('!');
}
When to Use Which?
什么时候用哪一种
#![allow(unused)]
fn main() {
// &str = borrowed read-only string view
// String = owned growable string
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
let s1 = "world";
let s2 = String::from("Rust");
greet(s1);
greet(&s2);
}
Practical Examples
实际操作对照
name = "alice"
upper = name.upper()
contains = "lic" in name
parts = "a,b,c".split(",")
joined = "-".join(["a", "b", "c"])
stripped = " hello ".strip()
replaced = name.replace("a", "A")
#![allow(unused)]
fn main() {
let name = "alice";
let upper = name.to_uppercase(); // String — new allocation
let contains = name.contains("lic"); // bool
let parts: Vec<&str> = "a,b,c".split(',').collect();
let joined = ["a", "b", "c"].join("-");
let stripped = " hello ".trim(); // &str — no allocation
let replaced = name.replace("a", "A"); // String
}
Python Developers: Think of it This Way
给 Python 开发者的记忆法
Python str ≈ Rust &str (read-only view most of the time)
Python str ≈ Rust String (when ownership or mutation matters)
Rule of thumb:
- Function parameters → use &str
- Struct fields → use String
- Return values → use String
- String literals → are &str
That rule of thumb will get you surprisingly far. It is not mathematically complete, but it is the right default instinct while learning.
这套口诀在入门阶段非常够用。它当然不是把所有细节都讲完了,但作为默认直觉基本是对的。
Printing and String Formatting
打印与字符串格式化
Basic Output
基础输出
print("Hello, World!")
print("Name:", name, "Age:", age)
print(f"Name: {name}, Age: {age}")
#![allow(unused)]
fn main() {
println!("Hello, World!");
println!("Name: {} Age: {}", name, age);
println!("Name: {name}, Age: {age}");
}
Format Specifiers
格式说明符
print(f"{3.14159:.2f}")
print(f"{42:05d}")
print(f"{255:#x}")
print(f"{42:>10}")
print(f"{'left':<10}|")
#![allow(unused)]
fn main() {
println!("{:.2}", 3.14159);
println!("{:05}", 42);
println!("{:#x}", 255);
println!("{:>10}", 42);
println!("{:<10}|", "left");
}
Debug Printing
调试输出
print(repr([1, 2, 3]))
from pprint import pprint
pprint({"key": [1, 2, 3]})
#![allow(unused)]
fn main() {
println!("{:?}", vec![1, 2, 3]);
println!("{:#?}", vec![1, 2, 3]);
#[derive(Debug)]
struct Point { x: f64, y: f64 }
let p = Point { x: 1.0, y: 2.0 };
println!("{:?}", p);
println!("{p:?}");
}
Quick Reference
速查表
| Python | Rust | Notes 说明 |
|---|---|---|
print(x) | println!("{}", x) or println!("{x}") | Display format 默认展示格式 |
print(repr(x)) | println!("{:?}", x) | Debug format 调试格式 |
f"Hello {name}" | format!("Hello {name}") | Returns String返回 String |
print(x, end="") | print!("{x}") | No newline 不换行 |
print(x, file=sys.stderr) | eprintln!("{x}") | Print to stderr 输出到标准错误 |
sys.stdout.write(s) | print!("{s}") | No newline 不自动加换行 |
Type Annotations: Optional vs Required
类型标注:可选与必须
Python Type Hints
Python 类型提示
def add(a: int, b: int) -> int:
return a + b
add(1, 2)
add("a", "b")
add(1, "2")
def find(key: str) -> int | None:
...
def first(items: list[int]) -> int | None:
return items[0] if items else None
UserId = int
Mapping = dict[str, list[int]]
Rust Type Declarations
Rust 类型声明
#![allow(unused)]
fn main() {
fn add(a: i32, b: i32) -> i32 {
a + b
}
add(1, 2);
// add("a", "b"); // ❌ Compile error
fn find(key: &str) -> Option<i32> {
Some(42)
}
fn first(items: &[i32]) -> Option<i32> {
items.first().copied()
}
type UserId = i64;
type Mapping = HashMap<String, Vec<i32>>;
}
Key insight: In Python, type hints mainly help tools such as IDEs and mypy. In Rust, types are part of the executable contract of the program itself, and the compiler enforces them all the time.
关键理解: Python 的类型提示主要是给 IDE 和 mypy 之类工具看的;Rust 的类型则是程序契约本身,编译器会一直强制执行。📌 See also: Ch. 6 — Enums and Pattern Matching for how Rust replaces Python’s
Unionand manyisinstance()-style checks.
📌 延伸阅读: 第 6 章——枚举与模式匹配 会继续展示 Rust 如何取代 Python 的Union和许多isinstance()式分支判断。
Exercises
练习
🏋️ Exercise: Temperature Converter
🏋️ 练习:温度转换器
Challenge: Write a function celsius_to_fahrenheit(c: f64) -> f64 and a classifier classify(temp_f: f64) -> &'static str that returns "cold"、"mild"、"hot" based on thresholds. Then print the results for 0、20、35 degrees Celsius using formatted output.
挑战:写一个函数 celsius_to_fahrenheit(c: f64) -> f64,再写一个分类函数 classify(temp_f: f64) -> &'static str,按阈值返回 "cold"、"mild"、"hot"。最后把 0、20、35 摄氏度对应的结果格式化打印出来。
🔑 Solution
🔑 参考答案
fn celsius_to_fahrenheit(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
fn classify(temp_f: f64) -> &'static str {
if temp_f < 50.0 { "cold" }
else if temp_f < 77.0 { "mild" }
else { "hot" }
}
fn main() {
for c in [0.0, 20.0, 35.0] {
let f = celsius_to_fahrenheit(c);
println!("{c:.1}°C = {f:.1}°F — {}", classify(f));
}
}
Key takeaway: Rust requires explicit f64, does not do implicit int-to-float conversion for you, iterates arrays directly with for, and treats if/else as expressions that can return values.
核心收获: Rust 需要显式 f64,不会偷偷替着做整数到浮点数的隐式转换;for 可以直接遍历数组;而 if/else 本身也是能返回值的表达式。
Control Flow §§ZH§§ 控制流
Conditional Statements
条件语句
What you’ll learn:
if/elsewithout parentheses but with braces,loop/while/forcompared with Python’s iteration model, expression blocks where everything can return a value, and function signatures with mandatory return types.
本章将学到什么: Rust 里的if/else不用括号但必须带花括号,loop/while/for和 Python 迭代模型的差别,什么叫表达式代码块,以及为什么函数签名里的返回类型必须写清楚。Difficulty: 🟢 Beginner
难度: 🟢 入门
if/else
if / else
# Python
if temperature > 100:
print("Too hot!")
elif temperature < 0:
print("Too cold!")
else:
print("Just right")
# Ternary
status = "hot" if temperature > 100 else "ok"
#![allow(unused)]
fn main() {
// Rust — braces required, no colons, `else if` not `elif`
if temperature > 100 {
println!("Too hot!");
} else if temperature < 0 {
println!("Too cold!");
} else {
println!("Just right");
}
// if is an EXPRESSION — returns a value
let status = if temperature > 100 { "hot" } else { "ok" };
}
Important Differences
几个重要差别
#![allow(unused)]
fn main() {
// 1. Condition must be a bool — no truthy/falsy
let x = 42;
// if x { } // Error: expected bool, found integer
if x != 0 { } // Explicit comparison required
// In Python, these are all truthy/falsy:
// if []: -> False (empty list)
// if "": -> False (empty string)
// if 0: -> False (zero)
// if None: -> False
// In Rust, ONLY bool works in conditions:
let items: Vec<i32> = vec![];
// if items { } // Error
if !items.is_empty() { } // Explicit check
let name = "";
// if name { } // Error
if !name.is_empty() { } // Explicit check
}
Rust refuses to guess whether a value should count as “truthy”. That feels stricter at first, but it also kills a whole class of accidental logic bugs.
Rust 不会替代码猜“这个值看起来算不算真”。刚开始会觉得它管得真宽,但反过来看,这也顺手消灭了一大类由隐式真值规则引起的逻辑 bug。
Loops and Iteration
循环与迭代
for Loops
for 循环
# Python
for i in range(5):
print(i)
for item in ["a", "b", "c"]:
print(item)
for i, item in enumerate(["a", "b", "c"]):
print(f"{i}: {item}")
for key, value in {"x": 1, "y": 2}.items():
print(f"{key} = {value}")
#![allow(unused)]
fn main() {
// Rust
for i in 0..5 { // range(5) -> 0..5
println!("{}", i);
}
for item in ["a", "b", "c"] { // direct iteration
println!("{}", item);
}
for (i, item) in ["a", "b", "c"].iter().enumerate() { // enumerate()
println!("{}: {}", i, item);
}
// HashMap iteration
use std::collections::HashMap;
let map = HashMap::from([("x", 1), ("y", 2)]);
for (key, value) in &map { // & borrows the map
println!("{} = {}", key, value);
}
}
Range Syntax
区间语法
| Python | Rust | Notes 说明 |
|---|---|---|
range(5) | 0..5 | Half-open (excludes end) 左闭右开,不包含结束值 |
range(1, 10) | 1..10 | Half-open 左闭右开 |
range(1, 11) | 1..=10 | Inclusive (includes end) 闭区间,包含结束值 |
range(0, 10, 2) | (0..10).step_by(2) | Step is a method, not syntax 步长是方法,不是语法关键字 |
while Loops
while 循环
# Python
count = 0
while count < 5:
print(count)
count += 1
# Infinite loop
while True:
data = get_input()
if data == "quit":
break
#![allow(unused)]
fn main() {
// Rust
let mut count = 0;
while count < 5 {
println!("{}", count);
count += 1;
}
// Infinite loop — use `loop`, not `while true`
loop {
let data = get_input();
if data == "quit" {
break;
}
}
// loop can return a value
let result = loop {
let input = get_input();
if let Ok(num) = input.parse::<i32>() {
break num; // break with a value
}
println!("Not a number, try again");
};
}
List Comprehensions vs Iterator Chains
列表推导式与迭代器链
# Python — list comprehensions
squares = [x ** 2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
pairs = [(x, y) for x in range(3) for y in range(3)]
#![allow(unused)]
fn main() {
// Rust — iterator chains (.map, .filter, .collect)
let squares: Vec<i32> = (0..10).map(|x| x * x).collect();
let evens: Vec<i32> = (0..20).filter(|x| x % 2 == 0).collect();
let pairs: Vec<(i32, i32)> = (0..3)
.flat_map(|x| (0..3).map(move |y| (x, y)))
.collect();
// These are LAZY — nothing runs until .collect()
// Python comprehensions are eager
// Rust iterators can be more efficient on large data
}
Python’s comprehension syntax is concise and eager. Rust’s iterator chains are more explicit, but the laziness means the optimizer has much more room to fuse operations and avoid temporary allocations.
Python 的推导式写起来很紧凑,而且默认是立即执行的。Rust 的迭代器链会显得更啰嗦一些,但它的惰性求值给了优化器更大空间,很多操作可以被融合起来,临时分配也更容易省掉。
Expression Blocks
表达式代码块
Everything in Rust is an expression, or at least very close to one. That is a big shift from Python, where if and for are mostly statements.
Rust 最大的语感差别之一,就是几乎什么都想做成表达式。和 Python 里 if、for 主要还是语句不同,这一套一开始挺容易把人绕晕。
# Python — if is a statement (except ternary)
if condition:
result = "yes"
else:
result = "no"
# Or ternary (limited to one expression)
result = "yes" if condition else "no"
#![allow(unused)]
fn main() {
// Rust — if is an expression (returns a value)
let result = if condition { "yes" } else { "no" };
// Blocks are expressions — the last line without semicolon is the value
let value = {
let x = 5;
let y = 10;
x + y // No semicolon -> value of the block
};
// match is an expression too
let description = match temperature {
t if t > 100 => "boiling",
t if t > 50 => "hot",
t if t > 20 => "warm",
_ => "cold",
};
}
The following diagram shows the conceptual difference between Python’s statement-based control flow and Rust’s expression-based style:
下面这张图把 Python 的“语句式控制流”和 Rust 的“表达式式控制流”对比了一下:
flowchart LR
subgraph Python ["Python - Statements<br/>Python:语句"]
P1["if condition:"] --> P2["result = 'yes'"]
P1 --> P3["result = 'no'"]
P2 --> P4["result used later<br/>稍后再使用 result"]
P3 --> P4
end
subgraph Rust ["Rust - Expressions<br/>Rust:表达式"]
R1["let result = if cond"] --> R2["{ 'yes' }"]
R1 --> R3["{ 'no' }"]
R2 --> R4["value returned directly<br/>值直接返回"]
R3 --> R4
end
style Python fill:#ffeeba
style Rust fill:#d4edda
The semicolon rule: In Rust, the last expression in a block without a semicolon becomes the block’s return value. Once a semicolon is added, it becomes a statement and the block yields
(). This trips up Python developers constantly at the beginning.
分号规则: 在 Rust 里,代码块最后一个没有分号的表达式会成为整个代码块的返回值;一旦补上分号,它就变成语句,整个块返回()。这条规则刚开始特别容易绊 Python 开发者一脚。
Functions and Type Signatures
函数与类型签名
Python Functions
Python 函数
# Python — types optional, dynamic dispatch
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
# Default args, *args, **kwargs
def flexible(*args, **kwargs):
pass
# First-class functions
def apply(f, x):
return f(x)
result = apply(lambda x: x * 2, 5) # 10
Rust Functions
Rust 函数
#![allow(unused)]
fn main() {
// Rust — types REQUIRED on function signatures, no defaults
fn greet(name: &str, greeting: &str) -> String {
format!("{}, {}!", greeting, name)
}
// No default arguments — use builder pattern or Option
fn greet_with_default(name: &str, greeting: Option<&str>) -> String {
let greeting = greeting.unwrap_or("Hello");
format!("{}, {}!", greeting, name)
}
// No *args/**kwargs — use slices or structs
fn sum_all(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
// First-class functions and closures
fn apply(f: fn(i32) -> i32, x: i32) -> i32 {
f(x)
}
let result = apply(|x| x * 2, 5); // 10
}
Return Values
返回值
# Python — return is explicit, None is implicit
def divide(a, b):
if b == 0:
return None # Or raise an exception
return a / b
#![allow(unused)]
fn main() {
// Rust — last expression is the return value
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
}
Multiple Return Values
多个返回值
# Python — return a tuple
def min_max(numbers):
return min(numbers), max(numbers)
lo, hi = min_max([3, 1, 4, 1, 5])
#![allow(unused)]
fn main() {
// Rust — return a tuple too
fn min_max(numbers: &[i32]) -> (i32, i32) {
let min = *numbers.iter().min().unwrap();
let max = *numbers.iter().max().unwrap();
(min, max)
}
let (lo, hi) = min_max(&[3, 1, 4, 1, 5]);
}
Methods: self vs &self vs &mut self
方法接收者:self、&self 与 &mut self
#![allow(unused)]
fn main() {
// In Python, `self` is always a mutable reference to the object.
// In Rust, you choose the ownership mode explicitly.
impl MyStruct {
fn new() -> Self { ... } // no self — "static method"
fn read_only(&self) { ... } // &self — immutable borrow
fn modify(&mut self) { ... } // &mut self — mutable borrow
fn consume(self) { ... } // self — takes ownership
}
// Python equivalent:
// class MyStruct:
// @classmethod
// def new(cls): ...
// def read_only(self): ...
// def modify(self): ...
// def consume(self): ...
}
Rust forces the method signature to declare whether the method only reads, mutates, or consumes the instance. Python keeps all of that implicit, which is more flexible but also much easier to misuse.
Rust 会在方法签名里明确写出这个方法到底只是读取、会修改,还是会直接吃掉对象本身。Python 把这些都放在隐式约定里,灵活是灵活,但也更容易把对象状态玩坏。
Exercises
练习
🏋️ Exercise: FizzBuzz with Expressions 🏋️ 练习:用表达式写 FizzBuzz
Challenge: Write FizzBuzz for 1..=30 using Rust’s expression-based match. Each number should print "Fizz"、"Buzz"、"FizzBuzz" or the number itself. Use match (n % 3, n % 5) as the controlling expression.
挑战题: 用 Rust 的表达式式 match 为 1..=30 写一个 FizzBuzz。每个数字要输出 "Fizz"、"Buzz"、"FizzBuzz" 或数字本身。要求用 match (n % 3, n % 5) 作为匹配表达式。
🔑 Solution 🔑 参考答案
fn main() {
for n in 1..=30 {
let result = match (n % 3, n % 5) {
(0, 0) => String::from("FizzBuzz"),
(0, _) => String::from("Fizz"),
(_, 0) => String::from("Buzz"),
_ => n.to_string(),
};
println!("{result}");
}
}
Key takeaway: match is an expression that returns a value, so there is no need to write a long if / elif / else chain. The _ wildcard plays the role of Python’s default branch.
要点: match 本身就是返回值的表达式,所以根本不需要拖一长串 if / elif / else。其中 _ 通配符就相当于 Python 里的默认分支。
Data Structures and Collections §§ZH§§ 数据结构与集合
Tuples and Destructuring
元组与解构
What you’ll learn: Rust tuples vs Python tuples, arrays and slices, structs (Rust’s replacement for classes),
Vec<T>vslist,HashMap<K,V>vsdict, and the newtype pattern for domain modeling.
本章将学习: Rust 元组和 Python 元组的区别、数组与切片、结构体这类用来替代类的核心数据结构、Vec<T>与list、HashMap<K,V>与dict的对应关系,以及用于领域建模的 newtype 模式。Difficulty: 🟢 Beginner
难度: 🟢 入门
Python Tuples
Python 元组
# Python — tuples are immutable sequences
point = (3.0, 4.0)
x, y = point # Unpacking
print(f"x={x}, y={y}")
# Tuples can hold mixed types
record = ("Alice", 30, True)
name, age, active = record
# Named tuples for clarity
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
p = Point(3.0, 4.0)
print(p.x) # Named access
Rust Tuples
Rust 元组
#![allow(unused)]
fn main() {
// Rust — tuples are fixed-size, typed, can hold mixed types
let point: (f64, f64) = (3.0, 4.0);
let (x, y) = point; // Destructuring (same as Python unpacking)
println!("x={x}, y={y}");
// Mixed types
let record: (&str, i32, bool) = ("Alice", 30, true);
let (name, age, active) = record;
// Access by index (unlike Python, uses .0 .1 .2 syntax)
let first = record.0; // "Alice"
let second = record.1; // 30
// Python: record[0]
// Rust: record.0 ← dot-index, not bracket-index
}
When to Use Tuples vs Structs
什么时候该用元组,什么时候该用结构体
#![allow(unused)]
fn main() {
// Tuples: quick grouping, function returns, temporary values
fn min_max(data: &[i32]) -> (i32, i32) {
(*data.iter().min().unwrap(), *data.iter().max().unwrap())
}
let (lo, hi) = min_max(&[3, 1, 4, 1, 5]);
// Structs: named fields, clear intent, methods
struct Point { x: f64, y: f64 }
// Rule of thumb:
// - 2-3 same-type fields → tuple is fine
// - Named fields needed → use struct
// - Methods needed → use struct
// (Same guidance as Python: tuple vs namedtuple vs dataclass)
}
元组适合做轻量打包,结构体适合承载有明确语义的数据。只要开始想给字段命名、给类型加方法、或者让别人一眼看懂字段含义,基本就该上 struct 了。
Tuples are good for light grouping, while structs are better once the data has real meaning. As soon as field names, methods, or readability start to matter, struct is usually the better choice.
Arrays and Slices
数组与切片
Python Lists vs Rust Arrays
Python 列表与 Rust 数组
# Python — lists are dynamic, heterogeneous
numbers = [1, 2, 3, 4, 5] # Can grow, shrink, hold mixed types
numbers.append(6)
mixed = [1, "two", 3.0] # Mixed types allowed
#![allow(unused)]
fn main() {
// Rust has TWO fixed-size vs dynamic concepts:
// 1. Array — fixed size, stack-allocated (no Python equivalent)
let numbers: [i32; 5] = [1, 2, 3, 4, 5]; // Size is part of the type!
// numbers.push(6); // ❌ Arrays can't grow
// Initialize all elements to same value:
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// 2. Slice — a view into an array or Vec (like Python slicing, but borrowed)
let slice: &[i32] = &numbers[1..4]; // [2, 3, 4] — a reference, not a copy!
// Python: numbers[1:4] creates a NEW list (copy)
// Rust: &numbers[1..4] creates a VIEW (no copy, no allocation)
}
Rust 这里分得很细:数组负责“固定大小的数据块”,切片负责“借一个窗口看看其中一段”。Python 开发者第一次看会觉得有点较真,但这套区分能把拷贝、分配和所有权说得非常清楚。
Rust draws a sharper line here: arrays represent fixed-size data, while slices represent borrowed views into existing data. It feels stricter at first, but it makes copying, allocation, and ownership far more explicit.
Practical Comparison
实际对照
# Python slicing — creates copies
data = [10, 20, 30, 40, 50]
first_three = data[:3] # New list: [10, 20, 30]
last_two = data[-2:] # New list: [40, 50]
reversed_data = data[::-1] # New list: [50, 40, 30, 20, 10]
#![allow(unused)]
fn main() {
// Rust slicing — creates views (references)
let data = [10, 20, 30, 40, 50];
let first_three = &data[..3]; // &[i32], view: [10, 20, 30]
let last_two = &data[3..]; // &[i32], view: [40, 50]
// No negative indexing — use .len()
let last_two = &data[data.len()-2..]; // &[i32], view: [40, 50]
// Reverse: use an iterator
let reversed: Vec<i32> = data.iter().rev().copied().collect();
}
Structs vs Classes
结构体与类
Python Classes
Python 类
# Python — class with __init__, methods, properties
from dataclasses import dataclass
@dataclass
class Rectangle:
width: float
height: float
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2.0 * (self.width + self.height)
def scale(self, factor: float) -> "Rectangle":
return Rectangle(self.width * factor, self.height * factor)
def __str__(self) -> str:
return f"Rectangle({self.width} x {self.height})"
r = Rectangle(10.0, 5.0)
print(r.area()) # 50.0
print(r) # Rectangle(10.0 x 5.0)
Rust Structs
Rust 结构体
// Rust — struct + impl blocks (no inheritance!)
#[derive(Debug, Clone)]
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// "Constructor" — associated function (no self)
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height } // Field shorthand when names match
}
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
fn scale(&self, factor: f64) -> Rectangle {
Rectangle::new(self.width * factor, self.height * factor)
}
}
// Display trait = Python's __str__
impl std::fmt::Display for Rectangle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Rectangle({} x {})", self.width, self.height)
}
}
fn main() {
let r = Rectangle::new(10.0, 5.0);
println!("{}", r.area()); // 50.0
println!("{}", r); // Rectangle(10 x 5)
}
flowchart LR
subgraph Python ["Python Object (Heap)<br/>Python 对象(堆上)"]
PH["PyObject Header<br/>(refcount + type ptr)<br/>对象头"]
PW["width: float obj<br/>width 浮点对象"]
PHT["height: float obj<br/>height 浮点对象"]
PD["__dict__"]
PH --> PW
PH --> PHT
PH --> PD
end
subgraph Rust ["Rust Struct (Stack)<br/>Rust 结构体(栈上)"]
RW["width: f64<br/>(8 bytes)"]
RH["height: f64<br/>(8 bytes)"]
RW --- RH
end
style Python fill:#ffeeba
style Rust fill:#d4edda
Memory insight: A Python
Rectangleobject has a 56-byte header + separate heap-allocated float objects. A RustRectangleis exactly 16 bytes on the stack — no indirection, no GC pressure.
内存视角: Python 的Rectangle对象除了对象头之外,还会再指向独立分配的浮点对象;Rust 的Rectangle则是两个f64紧挨着放在一起,总共 16 字节,没有额外间接层,也没有垃圾回收压力。📌 See also: Ch. 10 — Traits and Generics covers implementing traits like
Display,Debug, and operator overloading for your structs.
📌 延伸阅读: 第 10 章——Trait 与泛型 会继续讲如何为结构体实现Display、Debug以及运算符重载相关 trait。
Key Mapping: Python Dunder Methods → Rust Traits
关键映射:Python 双下方法 → Rust trait
| Python | Rust | Purpose 用途 |
|---|---|---|
__str__ | impl Display | Human-readable string 面向人的展示字符串 |
__repr__ | #[derive(Debug)] | Debug representation 调试表示 |
__eq__ | #[derive(PartialEq)] | Equality comparison 相等比较 |
__hash__ | #[derive(Hash)] | Hashable for maps and sets 可作为哈希键 |
__lt__, __le__, etc. | #[derive(PartialOrd, Ord)] | Ordering 排序与比较 |
__add__ | impl Add | + operator+ 运算符 |
__iter__ | impl Iterator | Iteration 迭代 |
__len__ | .len() method | Length 长度 |
__enter__/__exit__ | impl Drop | Cleanup 资源清理 |
__init__ | fn new() | Constructor by convention 约定俗成的构造函数 |
__getitem__ | impl Index | Indexing with []下标访问 |
__contains__ | .contains() method | Membership test 成员测试 |
No Inheritance — Composition Instead
没有继承,改用组合与 trait
# Python — inheritance
class Animal:
def __init__(self, name: str):
self.name = name
def speak(self) -> str:
raise NotImplementedError
class Dog(Animal):
def speak(self) -> str:
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self) -> str:
return f"{self.name} says Meow!"
#![allow(unused)]
fn main() {
// Rust — traits + composition (no inheritance)
trait Animal {
fn name(&self) -> &str;
fn speak(&self) -> String;
}
struct Dog { name: String }
struct Cat { name: String }
impl Animal for Dog {
fn name(&self) -> &str { &self.name }
fn speak(&self) -> String {
format!("{} says Woof!", self.name)
}
}
impl Animal for Cat {
fn name(&self) -> &str { &self.name }
fn speak(&self) -> String {
format!("{} says Meow!", self.name)
}
}
// Use trait objects for polymorphism (like Python's duck typing):
fn animal_roll_call(animals: &[&dyn Animal]) {
for a in animals {
println!("{}", a.speak());
}
}
}
Mental model: Python says “inherit behavior”. Rust says “implement contracts”. The practical outcome can look similar, but Rust avoids fragile base classes and diamond-inheritance-style surprises.
心智模型: Python 更像是在说“把行为继承下来”,Rust 更像是在说“把契约实现出来”。最后呈现的多态效果可能类似,但 Rust 会避开脆弱基类和菱形继承那类历史遗留麻烦。
Vec vs list
Vec 与 list
Vec<T> is Rust’s growable, heap-allocated array — the closest equivalent to Python’s list.Vec<T> 是 Rust 里可增长、分配在堆上的顺序容器,也是最接近 Python list 的类型。
Creating Vectors
创建向量
# Python
numbers = [1, 2, 3]
empty = []
repeated = [0] * 10
from_range = list(range(1, 6))
#![allow(unused)]
fn main() {
// Rust
let numbers = vec![1, 2, 3]; // vec! macro (like a list literal)
let empty: Vec<i32> = Vec::new(); // Empty vec (type annotation needed)
let repeated = vec![0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let from_range: Vec<i32> = (1..6).collect(); // [1, 2, 3, 4, 5]
}
Common Operations
常见操作
# Python list operations
nums = [1, 2, 3]
nums.append(4) # [1, 2, 3, 4]
nums.extend([5, 6]) # [1, 2, 3, 4, 5, 6]
nums.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6]
last = nums.pop() # 6, nums = [0, 1, 2, 3, 4, 5]
length = len(nums) # 6
nums.sort() # In-place sort
sorted_copy = sorted(nums) # New sorted list
nums.reverse() # In-place reverse
contains = 3 in nums # True
index = nums.index(3) # Index of first 3
#![allow(unused)]
fn main() {
// Rust Vec operations
let mut nums = vec![1, 2, 3];
nums.push(4); // [1, 2, 3, 4]
nums.extend([5, 6]); // [1, 2, 3, 4, 5, 6]
nums.insert(0, 0); // [0, 1, 2, 3, 4, 5, 6]
let last = nums.pop(); // Some(6), nums = [0, 1, 2, 3, 4, 5]
let length = nums.len(); // 6
nums.sort(); // In-place sort
let mut sorted_copy = nums.clone();
sorted_copy.sort(); // Sort a clone
nums.reverse(); // In-place reverse
let contains = nums.contains(&3); // true
let index = nums.iter().position(|&x| x == 3); // Some(index) or None
}
Quick Reference
速查表
| Python | Rust | Notes 说明 |
|---|---|---|
lst.append(x) | vec.push(x) | Append one element 追加一个元素 |
lst.extend(other) | vec.extend(other) | Append many elements 追加多个元素 |
lst.pop() | vec.pop() | Returns Option<T>返回 Option<T> |
lst.insert(i, x) | vec.insert(i, x) | Insert at position 在指定位置插入 |
lst.remove(x) | vec.retain(|v| v != &x) | Remove by value 按值删除通常用 retain |
del lst[i] | vec.remove(i) | Returns the removed element 删除并返回该元素 |
len(lst) | vec.len() | Length 长度 |
x in lst | vec.contains(&x) | Membership test 成员测试 |
lst.sort() | vec.sort() | In-place sort 原地排序 |
sorted(lst) | Clone + sort, or iterator | New sorted result 得到新的排序结果 |
lst[i] | vec[i] | Panics if out of bounds 越界会 panic |
lst.get(i, default) | vec.get(i) | Returns Option<&T>返回 Option<&T> |
lst[1:3] | &vec[1..3] | Returns a slice 返回切片视图 |
HashMap vs dict
HashMap 与 dict
HashMap<K, V> is Rust’s hash map — equivalent to Python’s dict.HashMap<K, V> 是 Rust 标准库里的哈希映射,对应 Python 的 dict。
Creating HashMaps
创建哈希映射
# Python
scores = {"Alice": 100, "Bob": 85}
empty = {}
from_pairs = dict([("x", 1), ("y", 2)])
comprehension = {k: v for k, v in zip(keys, values)}
#![allow(unused)]
fn main() {
// Rust
use std::collections::HashMap;
let scores = HashMap::from([("Alice", 100), ("Bob", 85)]);
let empty: HashMap<String, i32> = HashMap::new();
let from_pairs: HashMap<&str, i32> = [("x", 1), ("y", 2)].into_iter().collect();
let comprehension: HashMap<_, _> = keys.iter().zip(values.iter()).collect();
}
Common Operations
常见操作
# Python dict operations
d = {"a": 1, "b": 2}
d["c"] = 3 # Insert
val = d["a"] # 1 (KeyError if missing)
val = d.get("z", 0) # 0 (default if missing)
del d["b"] # Remove
exists = "a" in d # True
keys = list(d.keys()) # ["a", "c"]
values = list(d.values()) # [1, 3]
items = list(d.items()) # [("a", 1), ("c", 3)]
length = len(d) # 2
# setdefault / defaultdict
from collections import defaultdict
word_count = defaultdict(int)
for word in words:
word_count[word] += 1
#![allow(unused)]
fn main() {
// Rust HashMap operations
use std::collections::HashMap;
let mut d = HashMap::new();
d.insert("a", 1);
d.insert("b", 2);
d.insert("c", 3); // Insert or overwrite
let val = d["a"]; // 1 (panics if missing)
let val = d.get("z").copied().unwrap_or(0); // 0 (safe access)
d.remove("b"); // Remove
let exists = d.contains_key("a"); // true
let keys: Vec<_> = d.keys().collect();
let values: Vec<_> = d.values().collect();
let length = d.len();
// entry API = Python's setdefault / defaultdict pattern
let mut word_count: HashMap<&str, i32> = HashMap::new();
for word in words {
*word_count.entry(word).or_insert(0) += 1;
}
}
Quick Reference
速查表
| Python | Rust | Notes 说明 |
|---|---|---|
d[key] = val | d.insert(key, val) | Insert or replace 插入或覆盖 |
d[key] | d[&key] | Panics if missing 键不存在会 panic |
d.get(key) | d.get(&key) | Returns Option<&V>返回 Option<&V> |
d.get(key, default) | d.get(&key).unwrap_or(&default) | Provide default 提供默认值 |
key in d | d.contains_key(&key) | Key existence check 判断键是否存在 |
del d[key] | d.remove(&key) | Returns Option<V>删除并返回旧值 |
d.keys() | d.keys() | Iterator 返回迭代器 |
d.values() | d.values() | Iterator 返回迭代器 |
d.items() | d.iter() | Iterator of (&K, &V)产出键值对引用 |
len(d) | d.len() | Length 长度 |
d.update(other) | d.extend(other) | Bulk update 批量更新 |
defaultdict(int) | .entry().or_insert(0) | Entry API 用 entry API 表达 |
d.setdefault(k, v) | d.entry(k).or_insert(v) | Insert default if missing 缺失时插入默认值 |
Other Collections
其他常见集合
| Python | Rust | Notes 说明 |
|---|---|---|
set() | HashSet<T> | use std::collections::HashSet; |
collections.deque | VecDeque<T> | Double-ended queue 双端队列 |
heapq | BinaryHeap<T> | Max-heap by default 默认是最大堆 |
collections.OrderedDict | IndexMap (crate) | HashMap does not preserve insertion order标准 HashMap 不保留插入顺序 |
sortedcontainers.SortedList | BTreeSet<T> / BTreeMap<K,V> | Tree-based and ordered 基于树结构,天然有序 |
Exercises
练习
🏋️ Exercise: Word Frequency Counter
🏋️ 练习:单词频率统计器
Challenge: Write a function that takes a &str sentence and returns a HashMap<String, usize> of word frequencies, case-insensitive. In Python this is Counter(s.lower().split()). Translate it to Rust.
挑战:写一个函数,接收一个 &str 句子并返回 HashMap<String, usize>,按不区分大小写的方式统计词频。Python 里的写法可以看成 Counter(s.lower().split()),把它翻译成 Rust。
🔑 Solution
🔑 参考答案
use std::collections::HashMap;
fn word_frequencies(text: &str) -> HashMap<String, usize> {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
let key = word.to_lowercase();
*counts.entry(key).or_insert(0) += 1;
}
counts
}
fn main() {
let text = "the quick brown fox jumps over the lazy fox";
let freq = word_frequencies(text);
for (word, count) in &freq {
println!("{word}: {count}");
}
}
Key takeaway: HashMap::entry().or_insert() is Rust’s equivalent of Python’s defaultdict or Counter. The * dereference is needed because or_insert returns &mut usize.
核心收获: HashMap::entry().or_insert() 基本就是 Rust 里对应 defaultdict 或 Counter 的套路。这里要写 * 解引用,是因为 or_insert 返回的是 &mut usize。
Enums and Pattern Matching §§ZH§§ 枚举与模式匹配
Algebraic Data Types vs Union Types
代数数据类型与 Union 类型
What you’ll learn: Rust enums with data vs Python
Uniontypes, exhaustivematchvsmatch/case,Option<T>as a compile-time replacement forNone, and guard patterns.
本章将学到什么: Rust 里携带数据的 enum 和 PythonUnion类型的差别,穷尽式match和 Pythonmatch/case的区别,Option<T>如何在编译期替代None,以及 guard 模式的写法。Difficulty: 🟡 Intermediate
难度: 🟡 进阶
Python 3.10 introduced match statements and type unions. Rust’s enums go further — each variant can carry different data, and the compiler ensures you handle every case.
Python 3.10 引入了 match 语句和类型联合。Rust 的 enum 走得更远,每个变体都能携带不同数据,而且编译器会强制确保每一种情况都被处理到。
Python Union Types and Match
Python 的 Union 类型与 Match
# Python 3.10+ — structural pattern matching
from typing import Union
from dataclasses import dataclass
@dataclass
class Circle:
radius: float
@dataclass
class Rectangle:
width: float
height: float
@dataclass
class Triangle:
base: float
height: float
Shape = Union[Circle, Rectangle, Triangle] # Type alias
def area(shape: Shape) -> float:
match shape:
case Circle(radius=r):
return 3.14159 * r * r
case Rectangle(width=w, height=h):
return w * h
case Triangle(base=b, height=h):
return 0.5 * b * h
# No compiler warning if you miss a case!
# Adding a new shape? grep the codebase and hope you find all match blocks.
Rust Enums — Data-Carrying Variants
Rust 的枚举:携带数据的变体
#![allow(unused)]
fn main() {
// Rust — enum variants carry data, compiler enforces exhaustive matching
enum Shape {
Circle(f64), // Circle carries radius
Rectangle(f64, f64), // Rectangle carries width, height
Triangle { base: f64, height: f64 }, // Named fields also work
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle { base, height } => 0.5 * base * height,
// ❌ If you add Shape::Pentagon and forget to handle it here,
// the compiler refuses to build. No grep needed.
}
}
}
Key insight: Rust’s
matchis exhaustive — the compiler verifies you handle every variant. Add a new variant to an enum and the compiler tells you exactly whichmatchblocks need updating. Python’smatchhas no such guarantee.
关键认识:Rust 的match是 穷尽式 的,编译器会验证每个变体都被处理到。给枚举新增一个变体之后,编译器会准确指出哪些match代码块要补。Python 的match没有这层保证。
Enums Replace Multiple Python Patterns
Enum 可以替代好几种 Python 写法
# Python — several patterns that Rust enums replace:
# 1. String constants
STATUS_PENDING = "pending"
STATUS_ACTIVE = "active"
STATUS_CLOSED = "closed"
# 2. Python Enum (no data)
from enum import Enum
class Status(Enum):
PENDING = "pending"
ACTIVE = "active"
CLOSED = "closed"
# 3. Tagged unions (class + type field)
class Message:
def __init__(self, kind, **data):
self.kind = kind
self.data = data
# Message(kind="text", content="hello")
# Message(kind="image", url="...", width=100)
#![allow(unused)]
fn main() {
// Rust — one enum does all three and more
// 1. Simple enum (like Python's Enum)
enum Status {
Pending,
Active,
Closed,
}
// 2. Data-carrying enum (tagged union — type-safe!)
enum Message {
Text(String),
Image { url: String, width: u32, height: u32 },
Quit, // No data
Move { x: i32, y: i32 },
}
}
flowchart TD
E["enum Message"] --> T["Text(String)\n🏷️ tag=0 + String data"]
E --> I["Image { url, width, height }\n🏷️ tag=1 + 3 fields"]
E --> Q["Quit\n🏷️ tag=2 + no data"]
E --> M["Move { x, y }\n🏷️ tag=3 + 2 fields"]
style E fill:#d4edda,stroke:#28a745
style T fill:#fff3cd
style I fill:#fff3cd
style Q fill:#fff3cd
style M fill:#fff3cd
Memory insight: Rust enums are “tagged unions” — the compiler stores a discriminant tag + enough space for the largest variant. Python’s equivalent (
Union[str, dict, None]) has no compact representation.
内存层面的认识:Rust 的 enum 本质上是“带标签的联合体”,编译器会保存一个判别标签,再加上足够容纳最大变体的数据空间。Python 那种Union[str, dict, None]并没有这种紧凑的底层表示。📌 See also: Ch. 9 — Error Handling uses enums extensively —
Result<T, E>andOption<T>are just enums withmatch.
📌 延伸阅读:第 9 章:错误处理 会大量用到 enum,因为Result<T, E>和Option<T>本质上也只是可以被match的枚举。
#![allow(unused)]
fn main() {
fn process(msg: &Message) {
match msg {
Message::Text(content) => println!("Text: {content}"),
Message::Image { url, width, height } => {
println!("Image: {url} ({width}x{height})")
}
Message::Quit => println!("Quitting"),
Message::Move { x, y } => println!("Moving to ({x}, {y})"),
}
}
}
Exhaustive Pattern Matching
穷尽式模式匹配
Python’s match — Not Exhaustive
Python 的 match:不是穷尽式的
# Python — the wildcard case is optional, no compiler help
def describe(value):
match value:
case 0:
return "zero"
case 1:
return "one"
# If you forget the default, Python returns None silently.
# No warning, no error.
describe(42) # Returns None — a silent bug
Rust’s match — Compiler-Enforced
Rust 的 match:由编译器强制检查
#![allow(unused)]
fn main() {
// Rust — MUST handle every possible case
fn describe(value: i32) -> &'static str {
match value {
0 => "zero",
1 => "one",
// ❌ Compile error: non-exhaustive patterns: `i32::MIN..=-1_i32`
// and `2_i32..=i32::MAX` not covered
_ => "other", // _ = catch-all (required for open-ended types)
}
}
// For enums, NO catch-all needed — compiler knows all variants:
enum Color { Red, Green, Blue }
fn color_hex(c: Color) -> &'static str {
match c {
Color::Red => "#ff0000",
Color::Green => "#00ff00",
Color::Blue => "#0000ff",
// No _ needed — all variants covered
// Add Color::Yellow later → compiler error HERE
}
}
}
Pattern Matching Features
模式匹配的常用能力
#![allow(unused)]
fn main() {
// Multiple values (like Python's case 1 | 2 | 3:)
match value {
1 | 2 | 3 => println!("small"),
4..=9 => println!("medium"), // Range patterns
_ => println!("large"),
}
// Guards (like Python's case x if x > 0:)
match temperature {
t if t > 100 => println!("boiling"),
t if t < 0 => println!("freezing"),
t => println!("normal: {t}°"),
}
// Nested destructuring
let point = (3, (4, 5));
match point {
(0, _) => println!("on y-axis"),
(_, (0, _)) => println!("y=0"),
(x, (y, z)) => println!("x={x}, y={y}, z={z}"),
}
}
Option for None Safety
用 Option 取代 None 带来的不确定性
Option<T> is the most important Rust enum for Python developers. It replaces None with a type-safe alternative.
对 Python 开发者来说,Option<T> 是最重要的 Rust 枚举之一。它用一种类型安全的方式替代了 None。
Python None
Python 里的 None
# Python — None is a value that can appear anywhere
def find_user(user_id: int) -> dict | None:
users = {1: {"name": "Alice"}}
return users.get(user_id)
user = find_user(999)
# user is None — but nothing forces you to check!
print(user["name"]) # 💥 TypeError at runtime
Rust Option
Rust 里的 Option
#![allow(unused)]
fn main() {
// Rust — Option<T> forces you to handle the None case
fn find_user(user_id: i64) -> Option<User> {
let users = HashMap::from([(1, User { name: "Alice".into() })]);
users.get(&user_id).cloned()
}
let user = find_user(999);
// user is Option<User> — you CANNOT use it without handling None
// Method 1: match
match find_user(999) {
Some(user) => println!("Found: {}", user.name),
None => println!("Not found"),
}
// Method 2: if let (like Python's if (x := expr) is not None)
if let Some(user) = find_user(1) {
println!("Found: {}", user.name);
}
// Method 3: unwrap_or
let name = find_user(999)
.map(|u| u.name)
.unwrap_or_else(|| "Unknown".to_string());
// Method 4: ? operator (in functions that return Option)
fn get_user_name(id: i64) -> Option<String> {
let user = find_user(id)?; // Returns None early if not found
Some(user.name)
}
}
Option Methods — Python Equivalents
Option 常用方法与 Python 写法对照
| Pattern | Python | Rust |
|---|---|---|
| Check if exists 判断值是否存在 | if x is not None:if x is not None: | if let Some(x) = opt {if let Some(x) = opt { |
| Default value 默认值 | x or defaultx or default | opt.unwrap_or(default)opt.unwrap_or(default) |
| Default factory 默认值工厂 | x or compute()x or compute() | opt.unwrap_or_else(|| compute())opt.unwrap_or_else(|| compute()) |
| Transform if exists 存在时再变换 | f(x) if x else Nonef(x) if x else None | opt.map(f)opt.map(f) |
| Chain lookups 链式查找 | x and x.attr and x.attr.method()x and x.attr and x.attr.method() | opt.and_then(|x| x.method())opt.and_then(|x| x.method()) |
| Crash if None 遇到空值直接崩 | Not possible to prevent 语言层面没法阻止 | opt.unwrap() (panic) or opt.expect("msg")opt.unwrap() 或 opt.expect("msg") |
| Get or raise 取值,否则报错 | x if x else raisex if x else raise | opt.ok_or(Error)?opt.ok_or(Error)? |
Exercises
练习
🏋️ Exercise: Shape Area Calculator 🏋️ 练习:图形面积计算器
Challenge: Define an enum Shape with variants Circle(f64) (radius), Rectangle(f64, f64) (width, height), and Triangle(f64, f64) (base, height). Implement a method fn area(&self) -> f64 using match. Create one of each and print the area.
挑战题:定义一个枚举 Shape,它包含 Circle(f64) 表示半径、Rectangle(f64, f64) 表示宽高、以及 Triangle(f64, f64) 表示底和高。用 match 实现 fn area(&self) -> f64,然后分别创建三种图形并打印面积。
🔑 Solution 🔑 参考答案
use std::f64::consts::PI;
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64),
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(r) => PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle(b, h) => 0.5 * b * h,
}
}
}
fn main() {
let shapes = [
Shape::Circle(5.0),
Shape::Rectangle(4.0, 6.0),
Shape::Triangle(3.0, 8.0),
];
for shape in &shapes {
println!("Area: {:.2}", shape.area());
}
}
Key takeaway: Rust enums replace Python’s Union[Circle, Rectangle, Triangle] + isinstance() checks. The compiler ensures you handle every variant — adding a new shape without updating area() is a compile error.
关键结论:Rust 的 enum 可以直接替代 Python 里 Union[Circle, Rectangle, Triangle] 加上 isinstance() 判断的组合。编译器会确保每个变体都被处理到,如果新增了一种图形却没更新 area(),会直接编译失败。
Ownership and Borrowing §§ZH§§ 所有权与借用
Understanding Ownership
理解所有权
What you’ll learn: Why Rust has ownership, how move semantics differ from Python’s reference counting, how borrowing with
&and&mutworks, the basics of lifetimes, and when smart pointers such asBox、Rc、Arcbecome useful.
本章将学习: Rust 为什么需要所有权,移动语义和 Python 引用计数有什么根本区别,&与&mut的借用规则,生命周期基础,以及Box、Rc、Arc等智能指针在什么场景下有用。Difficulty: 🟡 Intermediate
难度: 🟡 进阶
This is often the hardest concept for Python developers. Python developers rarely think about who owns a value because the runtime and garbage collector handle cleanup. Rust gives each value exactly one owner and checks that ownership statically at compile time.
这通常是 Python 开发者最难迈过去的一道坎。Python 里几乎不用思考“这份数据到底归谁管”,因为运行时和垃圾回收会兜底;Rust 则要求每个值始终有且只有一个所有者,并且在编译期把这件事检查清楚。
Python: Shared References Everywhere
Python:到处都是共享引用
# Python — everything is a reference, gc cleans up
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] — surprise! a changed too
Both a and b point to the same list. Python’s runtime keeps a reference count and eventually frees the object when no references remain. Most of the time, that behavior feels convenient, but aliasing bugs can slip in quietly.
这里 a 和 b 指向的是同一份列表对象。Python 运行时会维护引用计数,等引用都没了再回收对象。日常写脚本时这种体验很省心,但别名共享带来的副作用 bug 也很容易悄悄混进来。
Rust: Single Ownership
Rust:单一所有权
#![allow(unused)]
fn main() {
let a = vec![1, 2, 3];
let b = a;
// println!("{:?}", a); // ❌ value used after move
println!("{:?}", b);
}
After let b = a;, ownership moves from a to b. The old binding becomes invalid. When b leaves scope, the vector is destroyed deterministically without a garbage collector.
执行 let b = a; 之后,所有权从 a 移动到 b,旧绑定立刻失效。等 b 离开作用域,这个向量就会被确定性地释放,不需要垃圾回收参与。
The Three Ownership Rules
所有权三条基本规则
1. Each value has exactly one owner.
2. When the owner goes out of scope, the value is dropped.
3. Ownership can move, but it does not duplicate automatically.
1. 每个值在同一时刻只有一个所有者。2. 所有者离开作用域时,值就会被释放。
3. 所有权可以转移,但不会自动复制。
Move Semantics — The Biggest Python Shock
移动语义:最容易把 Python 思维撞碎的地方
def process(data):
data.append(42)
my_list = [1, 2, 3]
process(my_list)
print(my_list) # [1, 2, 3, 42]
#![allow(unused)]
fn main() {
fn process(mut data: Vec<i32>) -> Vec<i32> {
data.push(42);
data
}
let my_vec = vec![1, 2, 3];
let my_vec = process(my_vec);
println!("{:?}", my_vec);
fn process_borrowed(data: &mut Vec<i32>) {
data.push(42);
}
let mut my_vec = vec![1, 2, 3];
process_borrowed(&mut my_vec);
println!("{:?}", my_vec);
}
Passing a Vec<i32> by value transfers ownership. Passing &mut Vec<i32> lends temporary mutable access instead. That distinction is the core of Rust API design.
按值传入 Vec<i32> 会把所有权一起交出去;传 &mut Vec<i32> 则只是暂时借出可变访问权。Rust 的 API 设计很多时候就是围着这个区别展开的。
Ownership Visualized
把所有权画出来看
Python: Rust:
a ──────┐ a ──→ [1, 2, 3]
├──→ [1, 2, 3]
b ──────┘ After: let b = a;
(a and b share one object) a (invalid, moved)
(refcount = 2) b ──→ [1, 2, 3]
(only b owns the data)
del a → refcount = 1 drop(b) → data freed
del b → refcount = 0 → freed (deterministic, no GC)
stateDiagram-v2
state "Python<br/>引用计数" as PY {
[*] --> a_owns: a = [1,2,3]
a_owns --> shared: b = a
shared --> b_only: del a
b_only --> freed: del b
note right of shared: a 和 b 指向同一对象
}
state "Rust<br/>所有权移动" as RS {
[*] --> a_owns2: let a = vec![1,2,3]
a_owns2 --> b_owns: let b = a
b_owns --> freed2: b leaves scope
note right of b_owns: a 在 move 之后失效
}
Move Semantics vs Reference Counting
移动语义与引用计数
Copy vs Move
Copy 与 Move
#![allow(unused)]
fn main() {
let x = 42;
let y = x;
println!("{x} {y}");
let s1 = String::from("hello");
let s2 = s1;
// println!("{s1}"); // ❌ moved
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{s1} {s2}");
}
Primitive scalar types are usually Copy; heap-owning types such as String and Vec are moved. If a deep copy is truly wanted, it must be requested explicitly with .clone().
标量基础类型通常实现了 Copy;像 String、Vec 这类拥有堆数据的类型则默认发生 move。如果确实想要深拷贝,就得显式写出 .clone()。
Python Developer’s Mental Model
给 Python 开发者的心智对照
Python: Rust:
───────── ─────
int, float, bool Copy types
list, dict, str Move types with ownership
shared references explicit borrowing
gc cleanup deterministic drop
deepcopy / list(x) clone()
When Python’s Sharing Model Causes Bugs
Python 共享模型什么时候容易埋雷
def remove_duplicates(items):
seen = set()
result = []
for item in items:
if item not in seen:
seen.add(item)
result.append(item)
return result
original = [1, 2, 2, 3, 3, 3]
alias = original
unique = remove_duplicates(alias)
#![allow(unused)]
fn main() {
use std::collections::HashSet;
fn remove_duplicates(items: &[i32]) -> Vec<i32> {
let mut seen = HashSet::new();
items.iter()
.filter(|&&item| seen.insert(item))
.copied()
.collect()
}
let original = vec![1, 2, 2, 3, 3, 3];
let unique = remove_duplicates(&original);
}
Borrowing the slice &[i32] guarantees the function cannot mutate the input collection. That sort of guarantee is usually only informal in Python code review, but in Rust it is enforced by the type signature itself.
函数参数写成 &[i32] 之后,就已经保证了函数体不能修改原始集合。这个保证在 Python 里往往只是“代码审查时大家心里有数”,而在 Rust 里是函数签名本身强制表达出来的。
Borrowing and Lifetimes
借用与生命周期
Borrowing = Lending a Book
把借用想成借书
Python: Everyone has a photocopy
Rust: One person owns the book.
- &book = many people can read
- &mut book = one person can edit
- book = hand ownership away
Borrowing lets a value stay owned by one place while other code temporarily accesses it. This is how Rust avoids both constant cloning and accidental aliasing bugs.
借用的意义在于:值仍然由一个地方持有,但其他代码可以临时访问它。这样既避免了处处克隆,又挡住了共享别名带来的副作用问题。
Borrowing Rules
借用规则
flowchart TD
R["Borrowing Rules<br/>借用规则"] --> IMM["✅ Many &T<br/>多个只读借用"]
R --> MUT["✅ One &mut T<br/>一个可变借用"]
R --> CONFLICT["❌ &T + &mut T<br/>不能同时存在"]
IMM --> SAFE["Multiple readers, safe<br/>多读安全"]
MUT --> SAFE2["Single writer, safe<br/>单写安全"]
CONFLICT --> ERR["Compile error<br/>编译报错"]
style IMM fill:#d4edda
style MUT fill:#d4edda
style CONFLICT fill:#f8d7da
style ERR fill:#f8d7da,stroke:#dc3545
#![allow(unused)]
fn main() {
let mut data = vec![1, 2, 3];
let a = &data;
let b = &data;
println!("{:?} {:?}", a, b);
let c = &mut data;
c.push(4);
// println!("{:?}", a); // ❌ immutable borrow still alive
}
Rust allows many immutable borrows or one mutable borrow, but never both at the same time. That rule eliminates data races and “modify while iterating” bugs before the program ever runs.
Rust 允许同时存在很多个不可变借用,或者存在一个可变借用,但绝不会让两种情况重叠。正是这条规则,让数据竞争和“遍历时修改集合”这类问题直接死在编译期。
Lifetimes — A Brief Introduction
生命周期:先抓住最小概念
#![allow(unused)]
fn main() {
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
}
Lifetimes answer one question: how long is a reference guaranteed to stay valid? Most of the time the compiler infers the answer automatically. Explicit lifetime annotations only appear when the relationships become ambiguous.
生命周期本质上只在回答一个问题:这个引用到底能保证活多久。大多数情况下编译器会自动推导,只有当引用之间的关系变复杂、推导不出来时,才需要手写生命周期标注。
For Python developers: do not get hung up on lifetimes too early. Start by understanding ownership and borrowing. Once that foundation is solid, lifetime annotations stop looking mystical.
给 Python 开发者的提醒: 别太早被生命周期吓住。先把所有权和借用真正吃透,生命周期标注后面自然就没那么玄了。
Smart Pointers
智能指针
Sometimes single ownership is too restrictive. Rust then offers explicit smart pointers that reintroduce shared ownership or interior mutability in controlled ways.
有些场景里,单一所有权确实会显得太紧。这时 Rust 会提供显式的智能指针,让共享所有权或内部可变性在受控条件下重新出现。
#![allow(unused)]
fn main() {
let boxed = Box::new(42);
use std::rc::Rc;
let shared = Rc::new(vec![1, 2, 3]);
let clone1 = Rc::clone(&shared);
let clone2 = Rc::clone(&shared);
use std::sync::Arc;
let thread_safe = Arc::new(vec![1, 2, 3]);
use std::cell::RefCell;
let cell = RefCell::new(42);
*cell.borrow_mut() = 99;
}
When to Use Each
各自适用场景
| Smart Pointer 智能指针 | Python Analogy 类比 | Use Case 使用场景 |
|---|---|---|
Box<T> | Normal allocation 普通拥有型堆分配 | Large data, recursive types, trait objects 大对象、递归类型、trait object |
Rc<T> | Python-style refcount 类似 Python 引用计数 | Shared ownership, single-threaded 单线程共享所有权 |
Arc<T> | Thread-safe refcount 线程安全引用计数 | Shared ownership, multi-threaded 多线程共享所有权 |
RefCell<T> | Runtime mutability escape hatch 运行时借用检查的逃生舱 | Interior mutability 内部可变性 |
Rc<RefCell<T>> | Python-like object graph 接近 Python 对象图模型 | Shared + mutable graph structures 共享且可变的图结构 |
Key insight:
Rc<RefCell<T>>can get close to Python-style semantics, but it is never the default. Rust makes shared mutable state an explicit design choice because it carries cost and complexity.
关键理解:Rc<RefCell<T>>能把语义拉回接近 Python 的共享可变模型,但它永远不是默认选项。Rust 把共享可变状态变成显式设计选择,就是因为它确实有成本,也确实更复杂。📌 See also: Ch. 13 — Concurrency for
Arc<Mutex<T>>and multi-threaded shared state.
📌 延伸阅读: 第 13 章——并发 会继续介绍Arc<Mutex<T>>这类多线程共享状态写法。
Exercises
练习
🏋️ Exercise: Spot the Borrow Checker Error
🏋️ 练习:找出借用检查器错误
Challenge: The following code contains three borrow-checker-related problems. Identify them and fix the code without using .clone().
挑战:下面这段代码里有 3 个和借用检查器相关的问题。把它们找出来,并且在不使用 .clone() 的前提下修好。
fn main() {
let mut names = vec!["Alice".to_string(), "Bob".to_string()];
let first = &names[0];
names.push("Charlie".to_string());
println!("First: {first}");
let greeting = make_greeting(names[0]);
println!("{greeting}");
}
fn make_greeting(name: String) -> String {
format!("Hello, {name}!")
}
🔑 Solution
🔑 参考答案
fn main() {
let mut names = vec!["Alice".to_string(), "Bob".to_string()];
let first = &names[0];
println!("First: {first}");
names.push("Charlie".to_string());
let greeting = make_greeting(&names[0]);
println!("{greeting}");
}
fn make_greeting(name: &str) -> String {
format!("Hello, {name}!")
}
Errors fixed:
修掉的问题:
- Immutable borrow + mutation:
firstis still borrowingnameswhenpushmay reallocate the vector.
1. 不可变借用与修改冲突:first还在借用names时,push可能触发重新分配。 - Move out of Vec:
names[0]tries to move aStringout of the vector.
2. 试图从Vec中搬出元素:names[0]会尝试把String从向量里移走。 - Function takes ownership unnecessarily:
make_greetingonly needs to read the name, so&stris enough.
3. 函数不该拿走所有权:make_greeting只是读取名字,参数写成&str就够了。
Crates and Modules §§ZH§§ crate 与模块
Rust Modules vs Python Packages
Rust 模块系统与 Python 包系统对照
What you’ll learn:
modandusevsimport, visibility (pub) vs Python’s convention-based privacy, Cargo.toml vs pyproject.toml, crates.io vs PyPI, and workspaces vs monorepos.
本章将学到什么:mod和use与 Pythonimport的对应关系,pub可见性与 Python 约定式私有的差别,Cargo.toml与pyproject.toml的角色对照,crates.io与 PyPI 的区别,以及 Rust workspace 和 Python monorepo 的不同。Difficulty: 🟢 Beginner
难度: 🟢 入门
Python Module System
Python 的模块系统
# Python — files are modules, directories with __init__.py are packages
# myproject/
# ├── __init__.py # Makes it a package
# ├── main.py
# ├── utils/
# │ ├── __init__.py # Makes utils a sub-package
# │ ├── helpers.py
# │ └── validators.py
# └── models/
# ├── __init__.py
# ├── user.py
# └── product.py
# Importing:
from myproject.utils.helpers import format_name
from myproject.models.user import User
import myproject.utils.validators as validators
Python 这套东西的特点是“默认能跑,靠约定成形”。文件天然就是模块,目录里有 __init__.py 就成了包。学起来轻松,结构也灵活。
但灵活的另一面就是边界偏松,公开不公开、应该从哪里导入、哪些名字只给内部用,很多时候都靠团队自觉。
Rust Module System
Rust 的模块系统
#![allow(unused)]
fn main() {
// Rust — mod declarations create the module tree, files provide content
// src/
// ├── main.rs # Crate root — declares modules
// ├── utils/
// │ ├── mod.rs # Module declaration (like __init__.py)
// │ ├── helpers.rs
// │ └── validators.rs
// └── models/
// ├── mod.rs
// ├── user.rs
// └── product.rs
// In src/main.rs:
mod utils; // Tells Rust to look for src/utils/mod.rs
mod models; // Tells Rust to look for src/models/mod.rs
use utils::helpers::format_name;
use models::user::User;
// In src/utils/mod.rs:
pub mod helpers; // Declares and re-exports helpers.rs
pub mod validators; // Declares and re-exports validators.rs
}
graph TD
A["main.rs<br/>(crate root)"] --> B["mod utils"]
A --> C["mod models"]
B --> D["utils/mod.rs"]
D --> E["helpers.rs"]
D --> F["validators.rs"]
C --> G["models/mod.rs"]
G --> H["user.rs"]
G --> I["product.rs"]
style A fill:#d4edda,stroke:#28a745
style D fill:#fff3cd,stroke:#ffc107
style G fill:#fff3cd,stroke:#ffc107
Python equivalent: Think of
mod.rsas__init__.py— it declares what the module exports. The crate root (main.rs/lib.rs) is like your top-level package__init__.py.
和 Python 的类比: 可以把mod.rs看成__init__.py,负责声明模块要暴露什么。crate 根,也就是main.rs或lib.rs,则类似顶层包的__init__.py。
Rust 的模块系统更明确,也更啰嗦一点。模块树不是靠目录结构自动脑补出来的,而是要用 mod 明明白白声明。
这套设计刚上手时会觉得有点拧巴,但好处是边界更稳定,导出关系更清楚,工程越大越有味道。
Key Differences
核心差异
| Concept 概念 | Python | Rust |
|---|---|---|
| Module = file 文件即模块 | ✅ Automatic ✅ 自动成立 | Must declare with mod需要显式 mod 声明 |
| Package = directory 目录即包 | __init__.py | mod.rs |
| Public by default 默认公开 | ✅ Everything ✅ 基本都能拿到 | ❌ Private by default ❌ 默认私有 |
| Make public 如何公开 | _prefix convention靠命名约定 | pub keyword靠 pub 关键字 |
| Import syntax 导入语法 | from x import y | use x::y; |
| Wildcard import 通配导入 | from x import * | use x::*; (discouraged)use x::*;,但一般不鼓励 |
| Relative imports 相对导入 | from . import sibling | use super::sibling; |
| Re-export 重新导出 | __all__ or explicit__all__ 或手工导出 | pub use inner::Thing; |
Visibility — Private by Default
可见性:默认私有
# Python — "we're all adults here"
class User:
def __init__(self):
self.name = "Alice" # Public (by convention)
self._age = 30 # "Private" (convention: single underscore)
self.__secret = "shhh" # Name-mangled (not truly private)
# Nothing stops you from accessing _age or even __secret
print(user._age) # Works fine
print(user._User__secret) # Works too (name mangling)
#![allow(unused)]
fn main() {
// Rust — private is enforced by the compiler
pub struct User {
pub name: String, // Public — anyone can access
age: i32, // Private — only this module can access
}
impl User {
pub fn new(name: &str, age: i32) -> Self {
User { name: name.to_string(), age }
}
pub fn age(&self) -> i32 { // Public getter
self.age
}
fn validate(&self) -> bool { // Private method
self.age > 0
}
}
// Outside the module:
let user = User::new("Alice", 30);
println!("{}", user.name); // ✅ Public
// println!("{}", user.age); // ❌ Compile error: field is private
println!("{}", user.age()); // ✅ Public method (getter)
}
这就是 Python 和 Rust 在“边界感”上的经典差别。Python 更像是在说:大家都是成年人,别乱碰。Rust 则直接说:哪些能碰,哪些不能碰,编译器提前写死。
写小脚本时前者很轻巧,写大工程时后者通常更省心,因为模块接口和内部实现分得更干净。
Crates vs PyPI Packages
crate 与 PyPI 包
Python Packages (PyPI)
Python 包与 PyPI
# Python
pip install requests # Install from PyPI
pip install "requests>=2.28" # Version constraint
pip freeze > requirements.txt # Lock versions
pip install -r requirements.txt # Reproduce environment
Rust Crates (crates.io)
Rust crate 与 crates.io
# Rust
cargo add reqwest # Install from crates.io (adds to Cargo.toml)
cargo add reqwest@0.12 # Version constraint
# Cargo.lock is auto-generated — no manual step
cargo build # Downloads and compiles dependencies
Python 的依赖管理历史包袱比较重,requirements.txt、setup.py、pyproject.toml、poetry、uv 各有各的玩法。Rust 这边基本统一交给 Cargo,体验就利索很多。
至少在“声明依赖、锁版本、复现环境”这件事上,Cargo 默认就把主流程打通了。
Cargo.toml vs pyproject.toml
Cargo.toml 与 pyproject.toml 对照
# Rust — Cargo.toml
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] } # With feature flags
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
[dev-dependencies]
mockall = "0.13"
pyproject.toml 更像 Python 世界逐步统一后的汇总入口,而 Cargo.toml 则从一开始就是 Rust 项目管理的中心。包信息、依赖、特性开关、构建入口,几乎都围着它转。
所以很多 Python 开发者第一次用 Cargo 时会有种感觉:这玩意怎么什么都给安排好了。
Essential Crates for Python Developers
Python 开发者常见库在 Rust 里的对应物
| Python Library Python 库 | Rust Crate Rust crate | Purpose 用途 |
|---|---|---|
requests | reqwest | HTTP client HTTP 客户端 |
json (stdlib)json 标准库 | serde_json | JSON parsing JSON 解析 |
pydantic | serde | Serialization/validation 序列化与部分校验 |
pathlib | std::path (stdlib)std::path 标准库 | Path handling 路径处理 |
os / shutil | std::fs (stdlib)std::fs 标准库 | File operations 文件操作 |
re | regex | Regular expressions 正则表达式 |
logging | tracing / log | Logging 日志 |
click / argparse | clap | CLI argument parsing 命令行参数解析 |
asyncio | tokio | Async runtime 异步运行时 |
datetime | chrono | Date and time 日期时间 |
pytest | Built-in + rstest内置测试 + rstest | Testing 测试 |
dataclasses | #[derive(...)] | Data structures 数据结构派生 |
typing.Protocol | Traits | Structural typing 结构化抽象 |
subprocess | std::process (stdlib)std::process 标准库 | Run external commands 执行外部命令 |
sqlite3 | rusqlite | SQLite |
sqlalchemy | diesel / sqlx | ORM / SQL toolkit ORM / SQL 工具链 |
fastapi | axum / actix-web | Web framework Web 框架 |
这张表并不是说“Python 库 A 完全等于 Rust 库 B”,而是帮忙建立迁移时的直觉地图。
很多时候 Rust 生态的抽象方式和 Python 不完全一样,但先知道该从哪块地找,效率会高很多。
Workspaces vs Monorepos
workspace 与 monorepo
Python Monorepo (typical)
典型的 Python monorepo
# Python monorepo (various approaches, no standard)
myproject/
├── pyproject.toml # Root project
├── packages/
│ ├── core/
│ │ ├── pyproject.toml # Each package has its own config
│ │ └── src/core/...
│ ├── api/
│ │ ├── pyproject.toml
│ │ └── src/api/...
│ └── cli/
│ ├── pyproject.toml
│ └── src/cli/...
# Tools: poetry workspaces, pip -e ., uv workspaces — no standard
Rust Workspace
Rust workspace
# Rust — Cargo.toml at root
[workspace]
members = [
"core",
"api",
"cli",
]
# Shared dependencies across workspace
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# Rust workspace structure — standardized, built into Cargo
myproject/
├── Cargo.toml # Workspace root
├── Cargo.lock # Single lock file for all crates
├── core/
│ ├── Cargo.toml # [dependencies] serde.workspace = true
│ └── src/lib.rs
├── api/
│ ├── Cargo.toml
│ └── src/lib.rs
└── cli/
├── Cargo.toml
└── src/main.rs
# Workspace commands
cargo build # Build everything
cargo test # Test everything
cargo build -p core # Build just the core crate
cargo test -p api # Test just the api crate
cargo clippy --all # Lint everything
Key insight: Rust workspaces are first-class, built into Cargo. Python monorepos require third-party tools (poetry, uv, pants) with varying levels of support. In a Rust workspace, all crates share a single
Cargo.lock, ensuring consistent dependency versions across the project.
关键理解: Rust workspace 是 Cargo 原生支持的一等能力。Python monorepo 往往需要额外工具来拼装,而且不同方案支持程度差异不小。Rust workspace 下所有 crate 共用一个Cargo.lock,依赖版本一致性更容易维持。
如果已经习惯 Python 大仓库那种“一堆工具互相套娃”的日常,第一次用 Rust workspace 时通常会有点舒服过头。因为它本来就是官方设计的一部分,不用再自己东拼西凑。
这也是 Rust 工程化体验比较整齐的一个重要原因。
Exercises
练习
🏋️ Exercise: Module Visibility 🏋️ 练习:模块可见性判断
Challenge: Given this module structure, predict which lines compile and which don’t:
挑战题: 给定下面这个模块结构,判断哪些行可以编译通过,哪些不行:
mod kitchen {
fn secret_recipe() -> &'static str { "42 spices" }
pub fn menu() -> &'static str { "Today's special" }
pub mod staff {
pub fn cook() -> String {
format!("Cooking with {}", super::secret_recipe())
}
}
}
fn main() {
println!("{}", kitchen::menu()); // Line A
println!("{}", kitchen::secret_recipe()); // Line B
println!("{}", kitchen::staff::cook()); // Line C
}
🔑 Solution 🔑 参考答案
- Line A: ✅ Compiles —
menu()ispub
A 行:✅ 可以编译,因为menu()是pub。 - Line B: ❌ Compile error —
secret_recipe()is private tokitchen
B 行:❌ 编译错误,因为secret_recipe()对kitchen外部是私有的。 - Line C: ✅ Compiles —
staff::cook()ispub, andcook()can accesssecret_recipe()viasuper::(child modules can access parent’s private items)
C 行:✅ 可以编译,因为staff::cook()是pub,而且子模块可以通过super::访问父模块的私有项。
Key takeaway: In Rust, child modules can see parent’s privates (like Python’s _private convention, but enforced). Outsiders cannot. This is the opposite of Python where _private is just a hint.
关键点: Rust 里子模块可以看到父模块的私有项,但外部模块不行。和 Python 里 _private 只是个提示不同,Rust 这里是编译器真管。
Error Handling §§ZH§§ 错误处理
Exceptions vs Result
异常与 Result 的对照
What you’ll learn:
Result<T, E>versustry/except, the?operator for concise error propagation, custom error types withthiserror,anyhowfor applications, and why explicit errors avoid hidden bugs.
本章将学习:Result<T, E>和try/except的根本差异,?如何简洁地传播错误,怎样用thiserror写自定义错误,为什么应用层常配合anyhow,以及显式错误为什么能挡住隐藏 bug。Difficulty: 🟡 Intermediate
难度: 🟡 进阶
This is one of the biggest mindset shifts for Python developers. Python throws exceptions from almost anywhere and catches them somewhere else if needed. Rust treats errors as values through Result<T, E>, and that means the call sites must face them explicitly.
这大概是 Python 开发者切换到 Rust 时最需要重新建立的一套思维。Python 可以在几乎任意位置抛异常,再在别的地方接住;Rust 则把错误当成值,通过 Result<T, E> 传递,调用方必须显式面对它。
Python Exception Handling
Python 的异常处理
# Python — exceptions can be thrown from anywhere
import json
def load_config(path: str) -> dict:
try:
with open(path) as f:
data = json.load(f) # Can raise JSONDecodeError
if "version" not in data:
raise ValueError("Missing version field")
return data
except FileNotFoundError:
print(f"Config file not found: {path}")
return {}
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")
return {}
# What other exceptions can this throw?
# IOError? PermissionError? UnicodeDecodeError?
# You can't tell from the function signature!
Rust Result-Based Error Handling
Rust 基于 Result 的错误处理
#![allow(unused)]
fn main() {
// Rust — errors are return values, visible in the function signature
use std::fs;
use serde_json::Value;
fn load_config(path: &str) -> Result<Value, ConfigError> {
let contents = fs::read_to_string(path)
.map_err(|e| ConfigError::FileError(e.to_string()))?;
let data: Value = serde_json::from_str(&contents)
.map_err(|e| ConfigError::ParseError(e.to_string()))?;
if data.get("version").is_none() {
return Err(ConfigError::MissingField("version".to_string()));
}
Ok(data)
}
#[derive(Debug)]
enum ConfigError {
FileError(String),
ParseError(String),
MissingField(String),
}
}
Key Differences
核心差异
Python: Rust:
───────── ─────
- Errors are exceptions (thrown) - Errors are values (returned)
- Hidden control flow (stack unwinding) - Explicit control flow (? operator)
- Can't tell what errors from signature - MUST see errors in return type
- Uncaught exceptions crash at runtime - Unhandled Results are compile warnings
- try/except is optional - Handling Result is required
- Broad except catches everything - match arms are exhaustive
Python:- 错误通常以异常形式抛出
- 控制流经常是隐藏的,得顺着运行栈猜
- 从函数签名里通常看不出会抛什么
- 没接住的异常会在运行时炸出来
- `try/except` 是可选的
- 宽泛的 `except` 很容易把各种问题一锅端
Rust:
- 错误通常作为返回值传出
- 控制流显式写在代码里,`?` 一眼能看见
- 函数返回类型必须把错误面暴露出来
- 没处理的 `Result` 会收到编译期提醒
- 调用方必须决定怎么处理 `Result`
- `match` 分支需要覆盖完整情况
The Two Result Variants
Result 的两个分支
#![allow(unused)]
fn main() {
enum Result<T, E> {
Ok(T), // Success — contains the value
Err(E), // Failure — contains the error
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
match divide(10.0, 0.0) {
Ok(result) => println!("Result: {result}"),
Err(msg) => println!("Error: {msg}"),
}
}
Ok(T) represents the success path. Err(E) carries the failure path as data. In Python those two flows often share one function body and split invisibly at runtime; in Rust they are encoded in the type itself.Ok(T) 代表成功分支,Err(E) 把失败分支也装进数据里。Python 往往让这两条路在运行时偷偷分岔,Rust 则把分岔点直接编码进类型本身。
The ? Operator
? 运算符
The ? operator is Rust’s explicit form of “propagate this error upward”. It serves a similar practical purpose to exception bubbling, but it is written directly in the code.? 运算符可以理解成“把这个错误继续往上交”的显式写法。它在效果上有点像异常继续冒泡,但和异常不同,它是明明白白写在代码里的。
Python — Implicit Propagation
Python:隐式传播
def read_username() -> str:
with open("config.txt") as f:
return f.readline().strip()
def greet():
name = read_username()
print(f"Hello, {name}!")
Errors can escape from open or readline, but that fact is not visible in the function signature.open 和 readline 都可能抛错,但从函数签名本身完全看不出来。
Rust — Explicit Propagation with ?
Rust:用 ? 显式传播
#![allow(unused)]
fn main() {
use std::fs;
use std::io;
fn read_username() -> Result<String, io::Error> {
let contents = fs::read_to_string("config.txt")?;
Ok(contents.lines().next().unwrap_or("").to_string())
}
fn greet() -> Result<(), io::Error> {
let name = read_username()?;
println!("Hello, {name}!");
Ok(())
}
}
Chaining with ?
用 ? 串联多步操作
def process_file(path: str) -> dict:
with open(path) as f:
text = f.read()
data = json.loads(text)
validate(data)
return transform(data)
#![allow(unused)]
fn main() {
fn process_file(path: &str) -> Result<Data, AppError> {
let text = fs::read_to_string(path)?;
let data: Value = serde_json::from_str(&text)?;
let validated = validate(&data)?;
let result = transform(&validated)?;
Ok(result)
}
}
flowchart TD
A["read_to_string(path)?<br/>读取文件"] -->|Ok| B["serde_json::from_str?<br/>解析 JSON"]
A -->|Err| X["Return Err(io::Error)<br/>提前返回 I/O 错误"]
B -->|Ok| C["validate(&data)?<br/>校验数据"]
B -->|Err| Y["Return Err(serde::Error)<br/>提前返回解析错误"]
C -->|Ok| D["transform(&validated)?<br/>转换数据"]
C -->|Err| Z["Return Err(ValidationError)<br/>提前返回校验错误"]
D -->|Ok| E["Ok(result) ✅"]
D -->|Err| W["Return Err(TransformError)<br/>提前返回转换错误"]
style E fill:#d4edda,stroke:#28a745
style X fill:#f8d7da,stroke:#dc3545
style Y fill:#f8d7da,stroke:#dc3545
style Z fill:#f8d7da,stroke:#dc3545
style W fill:#f8d7da,stroke:#dc3545
Each
?is a visible exit point. Unlike a Pythontryblock, Rust makes early-return locations obvious without reading external docs.
每个?都是可见的退出点。和 Python 的try块不同,Rust 不需要翻文档就能看见哪些地方会提前返回。📌 See also: Ch. 15 — Migration Patterns discusses how Python
try/exceptpatterns map into Rust in larger codebases.
📌 延伸阅读: 第 15 章——迁移模式 会继续讨论 Python 的try/except在真实 Rust 项目里该怎么迁移。
Custom Error Types with thiserror
用 thiserror 定义自定义错误类型
graph TD
AE["AppError (enum)<br/>应用错误枚举"] --> NF["NotFound<br/>{ entity, id }"]
AE --> VE["Validation<br/>{ field, message }"]
AE --> IO["Io(std::io::Error)<br/>#[from]"]
AE --> JSON["Json(serde_json::Error)<br/>#[from]"]
IO2["std::io::Error"] -->|"auto-convert via From<br/>通过 From 自动转换"| IO
JSON2["serde_json::Error"] -->|"auto-convert via From<br/>通过 From 自动转换"| JSON
style AE fill:#d4edda,stroke:#28a745
style NF fill:#fff3cd
style VE fill:#fff3cd
style IO fill:#fff3cd
style JSON fill:#fff3cd
style IO2 fill:#f8d7da
style JSON2 fill:#f8d7da
The
#[from]attribute generatesimpl From<io::Error> for AppError, so?can convert library errors into application errors automatically.#[from]会自动生成类似impl From<io::Error> for AppError的实现,因此?可以把库层错误自动提升成应用层错误。
Python Custom Exceptions
Python 的自定义异常
class AppError(Exception):
pass
class NotFoundError(AppError):
def __init__(self, entity: str, id: int):
self.entity = entity
self.id = id
super().__init__(f"{entity} with id {id} not found")
class ValidationError(AppError):
def __init__(self, field: str, message: str):
self.field = field
super().__init__(f"Validation error on {field}: {message}")
def find_user(user_id: int) -> dict:
if user_id not in users:
raise NotFoundError("User", user_id)
return users[user_id]
Rust Custom Errors with thiserror
Rust 里用 thiserror 写自定义错误
#![allow(unused)]
fn main() {
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("{entity} with id {id} not found")]
NotFound { entity: String, id: i64 },
#[error("Validation error on {field}: {message}")]
Validation { field: String, message: String },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
fn find_user(user_id: i64) -> Result<User, AppError> {
users.get(&user_id)
.cloned()
.ok_or(AppError::NotFound {
entity: "User".to_string(),
id: user_id,
})
}
fn load_users(path: &str) -> Result<Vec<User>, AppError> {
let data = fs::read_to_string(path)?;
let users: Vec<User> = serde_json::from_str(&data)?;
Ok(users)
}
}
Error Handling Quick Reference
错误处理速查表
| Python | Rust | Notes 说明 |
|---|---|---|
raise ValueError("msg") | return Err(AppError::Validation { ... }) | Explicit return 显式返回错误 |
try: ... except: | match result { Ok(v) => ..., Err(e) => ... } | Exhaustive 分支完整 |
except ValueError as e: | Err(AppError::Validation { .. }) => | Pattern match 模式匹配 |
raise ... from e | #[from] or .map_err() | Error chaining 错误链式转换 |
finally: | Drop trait | Deterministic cleanup 确定性清理 |
with open(...): | Scope-based drop | RAII pattern 作用域自动释放 |
| Exception propagates silently | ? propagates visibly | Always in return type 总会体现在返回类型里 |
isinstance(e, ValueError) | matches!(e, AppError::Validation {..}) | Type checking 基于类型匹配 |
Exercises
练习
🏋️ Exercise: Parse Config Value
🏋️ 练习:解析配置端口
Challenge: Write a function parse_port(s: &str) -> Result<u16, String> that rejects empty input, maps parsing failures into custom messages, and rejects privileged ports below 1024.
挑战:写一个 parse_port(s: &str) -> Result<u16, String>,要求拒绝空字符串,把解析错误改写成自定义消息,并拒绝 1024 以下的特权端口。
Call it with ""、"hello"、"80"、"8080" and print the results.
分别用 ""、"hello"、"80"、"8080" 调用它,并打印结果。
🔑 Solution
🔑 参考答案
fn parse_port(s: &str) -> Result<u16, String> {
if s.is_empty() {
return Err("empty input".to_string());
}
let port: u16 = s.parse().map_err(|e| format!("invalid number: {e}"))?;
if port < 1024 {
return Err(format!("port {port} is privileged"));
}
Ok(port)
}
fn main() {
for input in ["", "hello", "80", "8080"] {
match parse_port(input) {
Ok(port) => println!("✅ {input} → {port}"),
Err(e) => println!("❌ {input:?} → {e}"),
}
}
}
Key takeaway: ? plus .map_err() is the Rust replacement for “catch an exception, wrap it, and raise again”. Every error path stays visible in the function signature.
核心收获:? 配合 .map_err(),基本就是 Rust 对“接住异常、包装一下、再往外抛”的替代方案。区别在于,所有错误路径都会老老实实写进函数签名里。
Traits and Generics §§ZH§§ Trait 与泛型
Traits vs Duck Typing
Trait 与鸭子类型的对照
What you’ll learn: Traits as explicit contracts, how
Protocol(PEP 544) relates to traits, generic bounds withwhere, trait objects versus static dispatch, and the most important standard-library traits.
本章将学习: trait 如何把行为契约显式写出来,Protocol(PEP 544)和 trait 的关系,where子句里的泛型约束,trait object 与静态分发的差异,以及标准库里最重要的一批 trait。Difficulty: 🟡 Intermediate
难度: 🟡 进阶
This is one of the places where Rust’s type system starts to feel genuinely powerful to Python developers. Python says “if it quacks like a duck, use it.” Rust says “tell me exactly which duck behaviors are required, and I will verify them before the program runs.”
这一章往往是 Python 开发者第一次真正感受到 Rust 类型系统威力的地方。Python 的思路是“像鸭子就拿来用”;Rust 的思路是“把需要的鸭子行为写清楚,然后在程序运行前全部验证完”。
Python Duck Typing
Python 的鸭子类型
# Python — duck typing: anything with the right methods works
def total_area(shapes):
"""Works with anything that has an .area() method."""
return sum(shape.area() for shape in shapes)
class Circle:
def __init__(self, radius): self.radius = radius
def area(self): return 3.14159 * self.radius ** 2
class Rectangle:
def __init__(self, w, h): self.w, self.h = w, h
def area(self): return self.w * self.h
# Works at runtime — no inheritance needed!
shapes = [Circle(5), Rectangle(3, 4)]
print(total_area(shapes)) # 90.54
class Dog:
def bark(self): return "Woof!"
total_area([Dog()]) # 💥 AttributeError
Rust Traits — Explicit Duck Typing
Rust trait:显式版鸭子类型
#![allow(unused)]
fn main() {
trait HasArea {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
impl HasArea for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
fn total_area(shapes: &[&dyn HasArea]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}
let shapes: Vec<&dyn HasArea> = vec![&Circle { radius: 5.0 }, &Rectangle { width: 3.0, height: 4.0 }];
println!("{}", total_area(&shapes));
// struct Dog;
// total_area(&[&Dog {}]); // ❌ Compile error
}
Key insight: Python’s duck typing gives flexibility by postponing checks to runtime. Rust keeps much of that flexibility, but moves the verification to compile time.
关键理解: Python 的鸭子类型是把灵活性建立在“运行时再检查”之上;Rust 则试图保留这份灵活性,但把校验提前到编译期。
Protocols (PEP 544) vs Traits
Protocol(PEP 544)与 trait
Python’s Protocol is probably the closest built-in concept to Rust traits. The biggest difference is enforcement: Python mostly relies on tooling, while Rust makes the compiler负责到底。
Python 的 Protocol 大概是最接近 Rust trait 的概念。最大的差别还是执行力度:Python 主要依赖工具链提醒,Rust 则让编译器亲自负责到底。
Python Protocol
Python 的 Protocol
from typing import Protocol, runtime_checkable
@runtime_checkable
class Printable(Protocol):
def to_string(self) -> str: ...
class User:
def __init__(self, name: str):
self.name = name
def to_string(self) -> str:
return f"User({self.name})"
class Product:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
def to_string(self) -> str:
return f"Product({self.name}, ${self.price:.2f})"
def print_all(items: list[Printable]) -> None:
for item in items:
print(item.to_string())
Rust Trait
Rust 的 trait
#![allow(unused)]
fn main() {
trait Printable {
fn to_string(&self) -> String;
}
struct User { name: String }
struct Product { name: String, price: f64 }
impl Printable for User {
fn to_string(&self) -> String {
format!("User({})", self.name)
}
}
impl Printable for Product {
fn to_string(&self) -> String {
format!("Product({}, ${:.2})", self.name, self.price)
}
}
fn print_all(items: &[&dyn Printable]) {
for item in items {
println!("{}", item.to_string());
}
}
// print_all(&[&42i32]); // ❌ Compile error
}
Comparison Table
对照表
| Feature 特性 | Python Protocol | Rust Trait |
|---|---|---|
| Structural typing 结构化类型 | ✅ Implicit 隐式 | ❌ Explicit impl必须显式实现 |
| Checked at 检查时机 | Runtime or mypy 运行时或静态工具 | Compile time 编译期 |
| Default implementations 默认实现 | ❌ | ✅ |
| Can add to foreign types 能否扩展外部类型 | ❌ | ✅ Within orphan rules 在孤儿规则约束内可以 |
| Multiple protocols / traits 多协议 / 多 trait | ✅ | ✅ |
| Associated types 关联类型 | ❌ | ✅ |
| Generic constraints 泛型约束 | ✅ | ✅ |
Generic Constraints
泛型约束
Python Generics
Python 泛型
from typing import TypeVar, Sequence
T = TypeVar('T')
def first(items: Sequence[T]) -> T | None:
return items[0] if items else None
from typing import SupportsFloat
T = TypeVar('T', bound=SupportsFloat)
def average(items: Sequence[T]) -> float:
return sum(float(x) for x in items) / len(items)
Rust Generics with Trait Bounds
Rust:带 trait bound 的泛型
#![allow(unused)]
fn main() {
fn first<T>(items: &[T]) -> Option<&T> {
items.first()
}
fn average<T>(items: &[T]) -> f64
where
T: Into<f64> + Copy,
{
let sum: f64 = items.iter().map(|&x| x.into()).sum();
sum / items.len() as f64
}
fn log_and_clone<T: std::fmt::Display + std::fmt::Debug + Clone>(item: &T) -> T {
println!("Display: {}", item);
println!("Debug: {:?}", item);
item.clone()
}
fn print_it(item: &impl std::fmt::Display) {
println!("{}", item);
}
}
Generics Quick Reference
泛型速查表
| Python | Rust | Notes 说明 |
|---|---|---|
TypeVar('T') | <T> | Unbounded generic 无约束泛型 |
TypeVar('T', bound=X) | <T: X> | Bounded generic 有约束泛型 |
Union[int, str] | enum or trait object | No direct union type 没有直接等价的联合类型 |
Sequence[T] | &[T] | Borrowed sequence 借用切片 |
Callable[[A], R] | Fn(A) -> R | Function trait 函数 trait |
Optional[T] | Option<T> | Built into the language 语言内建 |
Common Standard Library Traits
常见标准库 trait
These are the Rust equivalents of many familiar Python magic methods. They describe how a type prints, compares, clones, iterates, and overloads operators.
这一组 trait 基本可以看成 Rust 世界里对很多 Python 魔术方法的对应抽象。类型怎么打印、怎么比较、怎么克隆、怎么迭代、怎么做运算符重载,基本都落在这里。
Display and Debug
Display 与 Debug
#![allow(unused)]
fn main() {
use std::fmt;
#[derive(Debug)]
struct Point { x: f64, y: f64 }
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
}
Comparison Traits
比较相关 trait
#![allow(unused)]
fn main() {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
struct Student {
name: String,
grade: i32,
}
let mut students = vec![
Student { name: "Charlie".into(), grade: 85 },
Student { name: "Alice".into(), grade: 92 },
];
students.sort();
}
Iterator Trait
Iterator trait
#![allow(unused)]
fn main() {
struct Countdown { value: i32 }
impl Iterator for Countdown {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
if self.value > 0 {
self.value -= 1;
Some(self.value + 1)
} else {
None
}
}
}
for n in (Countdown { value: 5 }) {
println!("{n}");
}
}
Common Traits at a Glance
常见 trait 一览
| Rust Trait | Python Equivalent | Purpose 用途 |
|---|---|---|
Display | __str__ | Human-readable string 面向人类的显示字符串 |
Debug | __repr__ | Debug string 调试输出 |
Clone | copy.deepcopy | Deep copy 显式深拷贝 |
Copy | Implicit scalar copy 标量自动复制 | Cheap implicit copy |
PartialEq / Eq | __eq__ | Equality comparison 相等性比较 |
PartialOrd / Ord | __lt__ etc. | Ordering 排序比较 |
Hash | __hash__ | Hash support 可哈希 |
Default | Default constructor pattern 默认值模式 | Default initialization |
From / Into | Conversion constructors 转换构造 | Type conversion |
Iterator | __iter__ / __next__ | Iteration |
Drop | __del__ / context cleanup析构或清理 | Cleanup |
Add, Sub, Mul | __add__ etc. | Operator overloading 运算符重载 |
Index | __getitem__ | [] indexing |
Deref | No close equivalent 无直接对应物 | Smart-pointer deref |
Send / Sync | No Python equivalent Python 无对应概念 | Thread-safety markers |
flowchart TB
subgraph Static ["Static Dispatch<br/>静态分发"]
G["fn notify(item: &impl Summary)"] --> M1["Compiled: notify_Article()"]
G --> M2["Compiled: notify_Tweet()"]
M1 --> O1["Inlined, zero-cost<br/>内联、零额外开销"]
M2 --> O2["Inlined, zero-cost<br/>内联、零额外开销"]
end
subgraph Dynamic ["Dynamic Dispatch<br/>动态分发"]
D["fn notify(item: &dyn Summary)"] --> VT["vtable lookup<br/>查 vtable"]
VT --> I1["Article::summarize()"]
VT --> I2["Tweet::summarize()"]
end
style Static fill:#d4edda
style Dynamic fill:#fff3cd
Python equivalent: Python always uses dynamic dispatch at runtime. Rust defaults to static dispatch, where the compiler specializes code for each concrete type.
dyn Traitis the opt-in form when runtime polymorphism is actually needed.
和 Python 的对照:Python 基本一直在做运行时动态分发;Rust 默认走静态分发,也就是编译器针对每个具体类型生成专门代码。只有确实需要运行时多态时,才主动使用dyn Trait。📌 See also: Ch. 11 — From/Into Traits for the conversion traits in more depth.
📌 延伸阅读: 第 11 章——From/Into Traits 会继续展开转换类 trait。
Associated Types
关联类型
Rust traits can declare associated types, which each implementor fills in with a concrete type. Python has no equally strong built-in mechanism for this.
Rust 的 trait 可以声明关联类型,由每个实现者填入具体类型。Python 没有一个同等强度、由语言层统一约束的对应机制。
#![allow(unused)]
fn main() {
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
struct Countdown { remaining: u32 }
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.remaining > 0 {
self.remaining -= 1;
Some(self.remaining)
} else {
None
}
}
}
}
Operator Overloading: __add__ → impl Add
运算符重载:__add__ → impl Add
class Vec2:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vec2(self.x + other.x, self.y + other.y)
#![allow(unused)]
fn main() {
use std::ops::Add;
#[derive(Debug, Clone, Copy)]
struct Vec2 { x: f64, y: f64 }
impl Add for Vec2 {
type Output = Vec2;
fn add(self, rhs: Vec2) -> Vec2 {
Vec2 { x: self.x + rhs.x, y: self.y + rhs.y }
}
}
let a = Vec2 { x: 1.0, y: 2.0 };
let b = Vec2 { x: 3.0, y: 4.0 };
let c = a + b;
}
The big difference is type checking. Python’s __add__ accepts whatever gets passed in and then may fail at runtime. Rust’s Add implementation fixes the operand types up front unless additional overloads are explicitly written.
最大的差别还是类型检查。Python 的 __add__ 先收下参数再说,类型不合适时运行时炸;Rust 的 Add 在实现时就把操作数类型钉死了,除非额外显式写更多重载。
Exercises
练习
🏋️ Exercise: Generic Summary Trait
🏋️ 练习:通用 Summary trait
Challenge: Define a trait Summary with fn summarize(&self) -> String, implement it for Article and Tweet, and then write fn notify(item: &impl Summary) to print the summary.
挑战:定义一个 Summary trait,要求包含 fn summarize(&self) -> String;为 Article 和 Tweet 实现它;然后再写一个 fn notify(item: &impl Summary) 用来打印摘要。
🔑 Solution
🔑 参考答案
trait Summary {
fn summarize(&self) -> String;
}
struct Article { title: String, body: String }
struct Tweet { username: String, content: String }
impl Summary for Article {
fn summarize(&self) -> String {
format!("{} — {}...", self.title, &self.body[..20.min(self.body.len())])
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
fn notify(item: &impl Summary) {
println!("📢 {}", item.summarize());
}
fn main() {
let article = Article {
title: "Rust is great".into(),
body: "Here is why Rust beats Python for systems...".into(),
};
let tweet = Tweet {
username: "rustacean".into(),
content: "Just shipped my first crate!".into(),
};
notify(&article);
notify(&tweet);
}
Key takeaway: &impl Summary is close in spirit to saying “anything that satisfies this protocol”, but Rust turns that promise into a compile-time guarantee rather than a runtime gamble.
核心收获: &impl Summary 的精神很接近“任何满足这个协议的东西都行”,但 Rust 会把这个承诺变成编译期保证,而不是留到运行时碰碰运气。
From and Into Traits §§ZH§§ From 与 Into Trait
Type Conversions in Rust
Rust 中的类型转换
What you’ll learn:
FromandIntofor zero-cost conversions,TryFromfor fallible conversions, howimpl From<A> for Bautomatically enablesInto<B> for A, and common string conversion patterns.
本章将学习:From和Into如何表达零成本类型转换,TryFrom如何处理可能失败的转换,为什么实现impl From<A> for B会自动得到Into<B> for A,以及常见的字符串转换写法。Difficulty: 🟡 Intermediate
难度: 🟡 进阶
Python usually handles conversion through constructors such as int("42")、str(42)、float("3.14")。Rust 则把类型转换抽象成 From 和 Into trait,用类型系统把可转换关系表达清楚。
Python 习惯通过构造函数完成转换,例如 int("42")、str(42)、float("3.14")。Rust 则把转换能力放进 From 和 Into trait 里,让可转换关系成为类型系统的一部分。
Python Type Conversion
Python 的类型转换
# Python — explicit constructors for conversion
x = int("42") # str → int (can raise ValueError)
s = str(42) # int → str
f = float("3.14") # str → float
lst = list((1, 2, 3)) # tuple → list
# Custom conversion via __init__ or class methods
class Celsius:
def __init__(self, temp: float):
self.temp = temp
@classmethod
def from_fahrenheit(cls, f: float) -> "Celsius":
return cls((f - 32.0) * 5.0 / 9.0)
c = Celsius.from_fahrenheit(212.0) # 100.0°C
Rust From/Into
Rust 的 From / Into
#![allow(unused)]
fn main() {
// Rust — From trait defines conversions
// Implementing From<T> gives you Into<U> automatically!
struct Celsius(f64);
struct Fahrenheit(f64);
impl From<Fahrenheit> for Celsius {
fn from(f: Fahrenheit) -> Self {
Celsius((f.0 - 32.0) * 5.0 / 9.0)
}
}
// Now both work:
let c1 = Celsius::from(Fahrenheit(212.0)); // Explicit From
let c2: Celsius = Fahrenheit(212.0).into(); // Into (automatically derived)
// String conversions:
let s: String = String::from("hello"); // &str → String
let s: String = "hello".to_string(); // Same thing
let s: String = "hello".into(); // Also works (From is implemented)
let num: i64 = 42i32.into(); // i32 → i64 (lossless, so From exists)
// let small: i32 = 42i64.into(); // ❌ i64 → i32 might lose data — no From
// For fallible conversions, use TryFrom:
let n: Result<i32, _> = "42".parse(); // str → i32 (might fail)
let n: i32 = "42".parse().unwrap(); // Panic if not a number
let n: i32 = "42".parse()?; // Propagate error with ?
}
From is where conversion logic lives. Into is the caller-facing convenience that appears automatically once From is implemented.
真正承载转换逻辑的是 From。而 Into 更像调用侧的便捷接口,只要 From 实现好了,它通常就会自动跟着出现。
The From/Into Relationship
From 和 Into 的关系
flowchart LR
A["impl From<A> for B<br/>为 B 实现 From<A>"] -->|"auto-generates<br/>自动生成"| B["impl Into<B> for A<br/>为 A 自动具备 Into<B>"]
C["Celsius::from(Fahrenheit(212.0))"] ---|"same as<br/>等价于"| D["Fahrenheit(212.0).into()"]
style A fill:#d4edda
style B fill:#d4edda
Rule of thumb: implement
From, notInto. OnceFrom<A> for Bexists, callers automatically getInto<B> for A.
经验法则: 优先实现From,几乎不要手写Into。只要有了From<A> for B,调用方自然就拥有Into<B> for A。
When to Use From/Into
什么时候适合用 From / Into
#![allow(unused)]
fn main() {
// Implement From<T> for your types to enable ergonomic API design:
#[derive(Debug)]
struct UserId(i64);
impl From<i64> for UserId {
fn from(id: i64) -> Self {
UserId(id)
}
}
// Now functions can accept anything convertible to UserId:
fn find_user(id: impl Into<UserId>) -> Option<String> {
let user_id = id.into();
// ... lookup logic
Some(format!("User #{:?}", user_id))
}
find_user(42i64); // ✅ i64 auto-converts to UserId
find_user(UserId(42)); // ✅ UserId stays as-is
}
This pattern makes APIs flexible without losing type safety. The function asks for “anything that can become UserId”, not for one concrete source type.
这种写法能在保持类型安全的前提下,把 API 做得更顺手。函数要的不是某一种死板的输入,而是“任何能转换成 UserId 的东西”。
TryFrom — Fallible Conversions
TryFrom:可能失败的转换
Not every conversion can succeed. Python typically raises exceptions in these cases. Rust uses TryFrom, which returns Result.
并不是所有转换都能保证成功。Python 通常在失败时抛异常,Rust 则用 TryFrom 把这件事建模成返回 Result。
# Python — fallible conversions raise exceptions
try:
port = int("not_a_number") # ValueError
except ValueError as e:
print(f"Invalid: {e}")
# Custom validation in __init__
class Port:
def __init__(self, value: int):
if not (1 <= value <= 65535):
raise ValueError(f"Invalid port: {value}")
self.value = value
try:
p = Port(99999) # ValueError at runtime
except ValueError:
pass
#![allow(unused)]
fn main() {
use std::num::ParseIntError;
// TryFrom for built-in types
let n: Result<i32, ParseIntError> = "42".try_into(); // Ok(42)
let n: Result<i32, ParseIntError> = "bad".try_into(); // Err(...)
// Custom TryFrom for validation
#[derive(Debug)]
struct Port(u16);
#[derive(Debug)]
enum PortError {
OutOfRange(u16),
Zero,
}
impl TryFrom<u16> for Port {
type Error = PortError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0 => Err(PortError::Zero),
1..=65535 => Ok(Port(value)),
// Note: u16 max is 65535, so this covers all cases
}
}
}
impl std::fmt::Display for PortError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PortError::Zero => write!(f, "port cannot be zero"),
PortError::OutOfRange(v) => write!(f, "port {v} out of range"),
}
}
}
// Usage:
let p: Result<Port, _> = 8080u16.try_into(); // Ok(Port(8080))
let p: Result<Port, _> = 0u16.try_into(); // Err(PortError::Zero)
}
Python → Rust mental model:
TryFromis similar to a validating constructor, but instead of throwing an exception it returnsResult, so callers must deal with the failure case explicitly.
Python → Rust 的思维转换:TryFrom很像“带校验的构造函数”,只是它不会抛异常,而是返回Result,于是调用方必须显式处理失败分支。
String Conversion Patterns
字符串转换模式
Strings are one of the most common conversion pain points for Python developers moving to Rust.
对从 Python 转到 Rust 的人来说,字符串转换往往是最容易绕晕的一块。
#![allow(unused)]
fn main() {
// String → &str (borrowing, free)
let s = String::from("hello");
let r: &str = &s; // Automatic Deref coercion
let r: &str = s.as_str(); // Explicit
// &str → String (allocating, costs memory)
let r: &str = "hello";
let s1 = String::from(r); // From trait
let s2 = r.to_string(); // ToString trait (via Display)
let s3: String = r.into(); // Into trait
// Number → String
let s = 42.to_string(); // "42" — like Python's str(42)
let s = format!("{:.2}", 3.14); // "3.14" — like Python's f"{3.14:.2f}"
// String → Number
let n: i32 = "42".parse().unwrap(); // like Python's int("42")
let f: f64 = "3.14".parse().unwrap(); // like Python's float("3.14")
// Custom types → String (implement Display)
use std::fmt;
struct Point { x: f64, y: f64 }
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
let p = Point { x: 1.0, y: 2.0 };
println!("{p}"); // (1, 2) — like Python's __str__
let s = p.to_string(); // Also works! Display gives you ToString for free.
}
Conversion Quick Reference
转换速查表
| Python | Rust | Notes 说明 |
|---|---|---|
str(x) | x.to_string() | Requires Display impl需要实现 Display |
int("42") | "42".parse::<i32>() | Returns Result返回 Result |
float("3.14") | "3.14".parse::<f64>() | Returns Result返回 Result |
list(iter) | iter.collect::<Vec<_>>() | Type annotation needed 通常需要类型标注 |
dict(pairs) | pairs.collect::<HashMap<_,_>>() | Type annotation needed 通常需要类型标注 |
bool(x) | No direct equivalent 没有完全对应物 | Use explicit checks 改成显式判断 |
MyClass(x) | MyClass::from(x) | Implement From<T>实现 From<T> |
MyClass(x) (validates) | MyClass::try_from(x)? | Implement TryFrom<T>实现 TryFrom<T> |
Conversion Chains and Error Handling
转换链与错误处理
Real applications often chain several conversions together. The contrast with Python becomes very obvious at that point.
真实代码往往会把多次转换串起来写,这时候 Rust 和 Python 的差别会一下子变得特别明显。
# Python — chain of conversions with try/except
def parse_config(raw: str) -> tuple[str, int]:
try:
host, port_str = raw.split(":")
port = int(port_str)
if not (1 <= port <= 65535):
raise ValueError(f"Bad port: {port}")
return (host, port)
except (ValueError, AttributeError) as e:
raise ConfigError(f"Invalid config: {e}") from e
fn parse_config(raw: &str) -> Result<(String, u16), String> {
let (host, port_str) = raw
.split_once(':')
.ok_or_else(|| "missing ':' separator".to_string())?;
let port: u16 = port_str
.parse()
.map_err(|e| format!("invalid port: {e}"))?;
if port == 0 {
return Err("port cannot be zero".to_string());
}
Ok((host.to_string(), port))
}
fn main() {
match parse_config("localhost:8080") {
Ok((host, port)) => println!("Connecting to {host}:{port}"),
Err(e) => eprintln!("Config error: {e}"),
}
}
Key insight: every
?is a visible exit point. In Python, any line insidetrymay throw; in Rust, only lines ending with?can short-circuit in that way.
关键理解: 每一个?都是一个肉眼可见的退出点。Python 的try块里几乎每一行都可能抛异常,而 Rust 会把这些可能提前返回的位置明确写在代码表面。📌 See also: Ch. 9 — Error Handling covers
Result、?和自定义错误类型的更多细节。
📌 延伸阅读: 第 9 章——错误处理 会继续深入Result、?与自定义错误类型。
Exercises
练习
🏋️ Exercise: Temperature Conversion Library
🏋️ 练习:温度转换库
Challenge: Build a small temperature conversion library.
挑战:做一个小型温度转换库。
- Define
Celsius(f64)、Fahrenheit(f64)、Kelvin(f64)structs.
1. 定义Celsius(f64)、Fahrenheit(f64)、Kelvin(f64)这三个结构体。 - Implement
From<Celsius> for FahrenheitandFrom<Celsius> for Kelvin.
2. 实现From<Celsius> for Fahrenheit和From<Celsius> for Kelvin。 - Implement
TryFrom<f64> for Kelvinand reject values below absolute zero.
3. 实现TryFrom<f64> for Kelvin,拒绝低于绝对零度的值。 - Implement
Displayfor all three types, such as"100.00°C".
4. 为三个类型都实现Display,输出像"100.00°C"这样的格式。
🔑 Solution
🔑 参考答案
use std::fmt;
struct Celsius(f64);
struct Fahrenheit(f64);
struct Kelvin(f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self {
Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
}
}
impl From<Celsius> for Kelvin {
fn from(c: Celsius) -> Self {
Kelvin(c.0 + 273.15)
}
}
#[derive(Debug)]
struct BelowAbsoluteZero;
impl fmt::Display for BelowAbsoluteZero {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "temperature below absolute zero")
}
}
impl TryFrom<f64> for Kelvin {
type Error = BelowAbsoluteZero;
fn try_from(value: f64) -> Result<Self, Self::Error> {
if value < 0.0 {
Err(BelowAbsoluteZero)
} else {
Ok(Kelvin(value))
}
}
}
impl fmt::Display for Celsius { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.2}°C", self.0) } }
impl fmt::Display for Fahrenheit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.2}°F", self.0) } }
impl fmt::Display for Kelvin { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:.2}K", self.0) } }
fn main() {
let boiling = Celsius(100.0);
let f: Fahrenheit = Celsius(100.0).into();
let k: Kelvin = Celsius(100.0).into();
println!("{boiling} = {f} = {k}");
match Kelvin::try_from(-10.0) {
Ok(k) => println!("{k}"),
Err(e) => println!("Error: {e}"),
}
}
Key takeaway: From handles infallible conversions, while TryFrom handles the ones that may fail. Python often folds both into __init__, but Rust makes the distinction explicit in the type system.
核心收获:From 负责一定成功的转换,TryFrom 负责可能失败的转换。Python 常把这两类事情都塞进 __init__,Rust 则明确把它们区分进类型系统。
Closures and Iterators §§ZH§§ 闭包与迭代器
Rust Closures vs Python Lambdas
Rust 闭包与 Python Lambda
What you’ll learn: Multi-line closures,
Fn/FnMut/FnOncecapture semantics, iterator chains vs list comprehensions,map/filter/fold, andmacro_rules!basics.
本章将学习: 多行闭包、Fn/FnMut/FnOnce的捕获语义、迭代器链与列表推导式的对应关系、map/filter/fold的基本用法,以及macro_rules!的入门概念。Difficulty: 🟡 Intermediate
难度: 🟡 进阶
Python Closures and Lambdas
Python 闭包与 Lambda
# Python — lambdas are one-expression anonymous functions
double = lambda x: x * 2
result = double(5) # 10
# Full closures capture variables from enclosing scope:
def make_adder(n):
def adder(x):
return x + n # Captures `n` from outer scope
return adder
add_5 = make_adder(5)
print(add_5(10)) # 15
# Higher-order functions:
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
Rust Closures
Rust 闭包
#![allow(unused)]
fn main() {
// Rust — closures use |args| body syntax
let double = |x: i32| x * 2;
let result = double(5); // 10
// Closures capture variables from enclosing scope:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n // `move` transfers ownership of `n` into the closure
}
let add_5 = make_adder(5);
println!("{}", add_5(10)); // 15
// Higher-order functions with iterators:
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
let evens: Vec<i32> = numbers.iter().filter(|&&x| x % 2 == 0).copied().collect();
}
Closure Syntax Comparison
闭包语法对照
Python: Rust:
───────── ─────
lambda x: x * 2 |x| x * 2
lambda x, y: x + y |x, y| x + y
lambda: 42 || 42
# Multi-line
def f(x): |x| {
y = x * 2 let y = x * 2;
return y + 1 y + 1
}
Rust 的闭包语法一开始看着像是在较劲,其实逻辑很统一:参数放在竖线里,后面接表达式或者代码块。真用熟了之后,反而比 Python 里 lambda 加外层函数定义那套更利索。
Rust closure syntax can look unusual at first, but the rule is consistent: put parameters between pipes, then follow with an expression or block. Once it clicks, it often feels cleaner than juggling Python lambdas and nested functions.
Closure Capture — How Rust Differs
闭包捕获:Rust 有什么不同
# Python — closures capture by reference (late binding!)
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2] — surprise! All captured the same `i`
# Fix with default arg trick:
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]
#![allow(unused)]
fn main() {
// Rust — closures capture correctly (no late-binding gotcha)
let funcs: Vec<Box<dyn Fn() -> i32>> = (0..3)
.map(|i| Box::new(move || i) as Box<dyn Fn() -> i32>)
.collect();
let results: Vec<i32> = funcs.iter().map(|f| f()).collect();
println!("{:?}", results); // [0, 1, 2] — correct!
// `move` captures a COPY of `i` for each closure — no late-binding surprise.
}
Python 里这个 late binding 坑,坑过一次基本就记一辈子。Rust 借助 move 和所有权语义,把每次循环里的值稳稳当当地放进各自闭包里,少了很多阴招。
Python’s late-binding surprise is memorable for all the wrong reasons. Rust avoids it by making capture behavior explicit through ownership and move.
Three Closure Traits
三种闭包 trait
#![allow(unused)]
fn main() {
// Rust closures implement one or more of these traits:
// Fn — can be called multiple times, doesn't mutate captures (most common)
fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(x) }
// FnMut — can be called multiple times, MAY mutate captures
fn apply_mut(mut f: impl FnMut(i32) -> i32, x: i32) -> i32 { f(x) }
// FnOnce — can only be called ONCE (consumes captures)
fn apply_once(f: impl FnOnce() -> String) -> String { f() }
// Python has no equivalent — closures are always Fn-like.
// In Rust, the compiler automatically determines which trait to use.
}
这三个 trait 讲的不是闭包长啥样,而是闭包会不会修改捕获值、会不会把捕获值消耗掉。理解这一层,很多编译器提示就顺眼多了。
These traits describe how a closure uses what it captures, not what it looks like. Once that clicks, many Rust compiler messages around closures become much easier to read.
Iterators vs Generators
迭代器与生成器
Python Generators
Python 生成器
# Python — generators with yield
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Lazy — values computed on demand
fib = fibonacci()
first_10 = [next(fib) for _ in range(10)]
# Generator expressions — like lazy list comprehensions
squares = (x ** 2 for x in range(1000000)) # No memory allocation
first_5 = [next(squares) for _ in range(5)]
Rust Iterators
Rust 迭代器
#![allow(unused)]
fn main() {
// Rust — Iterator trait (similar concept, different syntax)
struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci { a: 0, b: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let current = self.a;
self.a = self.b;
self.b = current + self.b;
Some(current)
}
}
// Lazy — values computed on demand (just like Python generators)
let first_10: Vec<u64> = Fibonacci::new().take(10).collect();
// Iterator chains — like generator expressions
let squares: Vec<u64> = (0..1_000_000u64).map(|x| x * x).take(5).collect();
}
Rust 没有 yield 这个语法糖,但 Iterator trait 把“按需产出下一个值”这件事固定成了统一接口。写法更工程化,组合能力也更强。
Rust does not use yield here, but the Iterator trait formalizes the same lazy “produce the next item on demand” behavior through a reusable interface.
Comprehensions vs Iterator Chains
推导式与迭代器链
This section maps Python’s comprehension syntax to Rust’s iterator chains.
这一节把 Python 的推导式写法,对应到 Rust 的迭代器链上。
List Comprehension → map/filter/collect
列表推导式 → map / filter / collect
# Python comprehensions:
squares = [x ** 2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
names = [user.name for user in users if user.active]
pairs = [(x, y) for x in range(3) for y in range(3)]
flat = [item for sublist in nested for item in sublist]
flowchart LR
A["Source<br/>[1,2,3,4,5]<br/>原始数据"] -->|.iter()| B["Iterator<br/>迭代器"]
B -->|.filter(|x| x%2==0)| C["[2, 4]<br/>筛选后"]
C -->|.map(|x| x*x)| D["[4, 16]<br/>映射后"]
D -->|.collect()| E["Vec<i32><br/>[4, 16]"]
style A fill:#ffeeba
style E fill:#d4edda
Key insight: Rust iterators are lazy — nothing happens until
.collect(). Python generators work similarly, but list comprehensions evaluate eagerly.
关键理解: Rust 迭代器默认是惰性的,通常到.collect()为止才真正执行。Python 生成器也类似,但列表推导式本身是立即求值的。
#![allow(unused)]
fn main() {
// Rust iterator chains:
let squares: Vec<i32> = (0..10).map(|x| x * x).collect();
let evens: Vec<i32> = (0..20).filter(|x| x % 2 == 0).collect();
let names: Vec<&str> = users.iter()
.filter(|u| u.active)
.map(|u| u.name.as_str())
.collect();
let pairs: Vec<(i32, i32)> = (0..3)
.flat_map(|x| (0..3).map(move |y| (x, y)))
.collect();
let flat: Vec<i32> = nested.iter()
.flat_map(|sublist| sublist.iter().copied())
.collect();
}
Dict Comprehension → collect into HashMap
字典推导式 → 收集成 HashMap
# Python
word_lengths = {word: len(word) for word in words}
inverted = {v: k for k, v in mapping.items()}
#![allow(unused)]
fn main() {
// Rust
let word_lengths: HashMap<&str, usize> = words.iter()
.map(|w| (*w, w.len()))
.collect();
let inverted: HashMap<&V, &K> = mapping.iter()
.map(|(k, v)| (v, k))
.collect();
}
Set Comprehension → collect into HashSet
集合推导式 → 收集成 HashSet
# Python
unique_lengths = {len(word) for word in words}
#![allow(unused)]
fn main() {
// Rust
let unique_lengths: HashSet<usize> = words.iter()
.map(|w| w.len())
.collect();
}
Common Iterator Methods
常见迭代器方法
| Python | Rust | Notes 说明 |
|---|---|---|
map(f, iter) | .map(f) | Transform each element 转换每个元素 |
filter(f, iter) | .filter(f) | Keep matching elements 保留满足条件的元素 |
sum(iter) | .sum() | Sum all elements 求和 |
min(iter) / max(iter) | .min() / .max() | Returns Option返回 Option |
any(f(x) for x in iter) | .any(f) | True if any match 任一满足即可 |
all(f(x) for x in iter) | .all(f) | True if all match 全部满足才为真 |
enumerate(iter) | .enumerate() | Index + value 返回索引和值 |
zip(a, b) | a.zip(b) | Pair elements 把两个迭代器配对 |
len(list) | .count() or .len() | Counting may consume iterators 计数可能会消耗迭代器 |
list(reversed(x)) | .rev() | Reverse iteration 反向迭代 |
itertools.chain(a, b) | a.chain(b) | Concatenate iterators 拼接迭代器 |
next(iter) | .next() | Get next element 取下一个元素 |
next(iter, default) | .next().unwrap_or(default) | With default 带默认值 |
list(iter) | .collect::<Vec<_>>() | Materialize into collection 物化成集合 |
sorted(iter) | Collect, then .sort() | No lazy sorted iterator 一般先收集再排序 |
functools.reduce(f, iter) | .fold(init, f) or .reduce(f) | Accumulate 累计归约 |
Key Differences
关键差异
Python iterators: Rust iterators:
───────────────── ──────────────
- Lazy by default (generators) - Lazy by default (all iterator chains)
- yield creates generators - impl Iterator { fn next() }
- StopIteration to end - None to end
- Can be consumed once - Can be consumed once
- No type safety - Fully type-safe
- Slightly slower (interpreter) - Zero-cost (compiled away)
Why Macros Exist in Rust
Rust 为什么需要宏
Python has no macro system. It relies on decorators, metaclasses, and runtime introspection for metaprogramming. Rust instead uses macros for compile-time code generation.
Python 没有真正意义上的宏系统,元编程主要依赖装饰器、元类和运行时反射。Rust 则把这类能力前移到编译期,用宏来生成代码。
Python Metaprogramming vs Rust Macros
Python 元编程与 Rust 宏
# Python — decorators and metaclasses for metaprogramming
from dataclasses import dataclass
from functools import wraps
@dataclass # Generates __init__, __repr__, __eq__ at import time
class Point:
x: float
y: float
# Custom decorator
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def process(data):
return data.upper()
#![allow(unused)]
fn main() {
// Rust — derive macros and declarative macros for code generation
#[derive(Debug, Clone, PartialEq)] // Generates Debug, Clone, PartialEq impls at COMPILE time
struct Point {
x: f64,
y: f64,
}
// Declarative macro (like a template)
macro_rules! log_call {
($func_name:expr, $body:expr) => {
println!("Calling {}", $func_name);
$body
};
}
fn process(data: &str) -> String {
log_call!("process", data.to_uppercase())
}
}
Common Built-in Macros
常见内建宏
#![allow(unused)]
fn main() {
// These macros are used everywhere in Rust:
println!("Hello, {}!", name); // Print with formatting
format!("Value: {}", x); // Create formatted String
vec![1, 2, 3]; // Create a Vec
assert_eq!(2 + 2, 4); // Test assertion
assert!(value > 0, "must be positive"); // Boolean assertion
dbg!(expression); // Debug print: prints expression AND value
todo!(); // Placeholder — compiles but panics if reached
unimplemented!(); // Mark code as unimplemented
panic!("something went wrong"); // Crash with message (like raise RuntimeError)
// Why are these macros instead of functions?
// - println! accepts variable arguments (Rust functions can't)
// - vec! generates code for any type and size
// - assert_eq! knows the SOURCE CODE of what you compared
// - dbg! knows the FILE NAME and LINE NUMBER
}
宏本质上就是“编译前展开的代码模板”。听着挺猛,但 Rust 把常用宏控制得很规矩,所以平时写起来并不会妖里妖气。
Macros are essentially code templates expanded before compilation. That sounds powerful, but Rust keeps common macro usage disciplined enough to remain readable in everyday code.
Writing a Simple Macro with macro_rules!
用 macro_rules! 写一个简单宏
#![allow(unused)]
fn main() {
// Python dict() equivalent
// Python: d = dict(a=1, b=2)
// Rust: let d = hashmap!{ "a" => 1, "b" => 2 };
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {
{
let mut map = std::collections::HashMap::new();
$(map.insert($key, $value);)*
map
}
};
}
let scores = hashmap! {
"Alice" => 100,
"Bob" => 85,
"Charlie" => 90,
};
}
Derive Macros — Auto-Implementing Traits
派生宏:自动实现 trait
#![allow(unused)]
fn main() {
// #[derive(...)] is the Rust equivalent of Python's @dataclass decorator
// Python:
// @dataclass(frozen=True, order=True)
// class Student:
// name: str
// grade: int
// Rust:
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Student {
name: String,
grade: i32,
}
// Common derive macros:
// Debug → {:?} formatting (like __repr__)
// Clone → .clone() deep copy
// Copy → implicit copy (only for simple types)
// PartialEq, Eq → == comparison (like __eq__)
// PartialOrd, Ord → <, >, sorting (like __lt__ etc.)
// Hash → usable as HashMap key (like __hash__)
// Default → MyType::default() (like __init__ with no args)
// Crate-provided derive macros:
// Serialize, Deserialize (serde) → JSON/YAML/TOML serialization
// (like Python's json.dumps/loads but type-safe)
}
Python Decorator vs Rust Derive
Python 装饰器与 Rust 派生宏
| Python Decorator | Rust Derive | Purpose 用途 |
|---|---|---|
@dataclass | #[derive(Debug, Clone, PartialEq)] | Data class 数据类 |
@dataclass(frozen=True) | Immutable by default | Immutability 默认不可变 |
@dataclass(order=True) | #[derive(Ord, PartialOrd)] | Comparison and sorting 比较与排序 |
@total_ordering | #[derive(PartialOrd, Ord)] | Full ordering 完整排序能力 |
json.dumps(obj.__dict__) | #[derive(Serialize)] | Serialization 序列化 |
MyClass(**json.loads(s)) | #[derive(Deserialize)] | Deserialization 反序列化 |
Exercises
练习
🏋️ Exercise: Derive and Custom Debug
🏋️ 练习:派生与自定义 Debug
Challenge: Create a User struct with fields name: String, email: String, and password_hash: String. Derive Clone and PartialEq, but implement Debug manually so it prints the name and email while redacting the password as "***".
挑战:创建一个 User 结构体,包含 name: String、email: String 和 password_hash: String 三个字段。为它派生 Clone 和 PartialEq,但手写 Debug,让调试输出只显示用户名和邮箱,密码位置统一显示成 "***"。
🔑 Solution
🔑 参考答案
use std::fmt;
#[derive(Clone, PartialEq)]
struct User {
name: String,
email: String,
password_hash: String,
}
impl fmt::Debug for User {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("User")
.field("name", &self.name)
.field("email", &self.email)
.field("password_hash", &"***")
.finish()
}
}
fn main() {
let user = User {
name: "Alice".into(),
email: "alice@example.com".into(),
password_hash: "a1b2c3d4e5f6".into(),
};
println!("{user:?}");
// Output: User { name: "Alice", email: "alice@example.com", password_hash: "***" }
}
Key takeaway: Rust lets you derive Debug for free, but it also lets you override the behavior when sensitive fields need special treatment. That is much safer than casually printing Python objects and hoping nothing private leaks.
核心收获: Rust 虽然可以很方便地自动派生 Debug,但一旦涉及敏感字段,也能随时手写覆盖。这比在 Python 里随手 print(obj) 然后祈祷别把隐私打出去要稳当得多。
Concurrency §§ZH§§ 并发
No GIL: True Parallelism
没有 GIL:真正的并行
What you’ll learn: Why the GIL limits Python concurrency, Rust’s
Send/Synctraits for compile-time thread safety,Arc<Mutex<T>>vs Pythonthreading.Lock, channels vsqueue.Queue, and async/await differences.
本章将学习: GIL 为什么会限制 Python 并发,Rust 如何用Send/Sync在编译期保证线程安全,Arc<Mutex<T>>与 Pythonthreading.Lock的对应关系,通道与queue.Queue的区别,以及两门语言中 async/await 的差异。Difficulty: 🔴 Advanced
难度: 🔴 高级
The GIL is Python’s biggest limitation for CPU-bound work. Rust has no GIL, so threads can run truly in parallel, and the type system prevents data races at compile time.
对于 CPU 密集任务来说,GIL 基本就是 Python 最大的天花板。Rust 没有这一层限制,线程可以真并行,而数据竞争则由类型系统在编译期拦下来。
gantt
title CPU-bound Work: Python GIL vs Rust Threads
dateFormat X
axisFormat %s
section Python (GIL)
Thread 1 :a1, 0, 4
Thread 2 :a2, 4, 8
Thread 3 :a3, 8, 12
Thread 4 :a4, 12, 16
section Rust (no GIL)
Thread 1 :b1, 0, 4
Thread 2 :b2, 0, 4
Thread 3 :b3, 0, 4
Thread 4 :b4, 0, 4
Key insight: Python threads run sequentially for CPU work because the GIL serializes them. Rust threads run truly in parallel, so four threads can approach a four-times speedup on the right workload.
关键理解: Python 线程在 CPU 任务上往往还是串着跑,因为 GIL 会把执行权串行化;Rust 线程则能真正并行,在合适负载下四个线程就有机会逼近四倍吞吐。📌 Prerequisite: Be comfortable with Ch. 7 — Ownership and Borrowing before this chapter.
Arc、Mutex、move闭包这些东西,底层全都踩在所有权模型上。
📌 前置建议: 在读这一章前,最好已经吃透 第 7 章——所有权与借用。Arc、Mutex、move闭包这些概念,底层都建立在所有权模型之上。
Python’s GIL Problem
Python 的 GIL 问题
# Python — threads don't help for CPU-bound work
import threading
import time
counter = 0
def increment(n):
global counter
for _ in range(n):
counter += 1 # NOT thread-safe! But GIL "protects" simple operations
threads = [threading.Thread(target=increment, args=(1_000_000,)) for _ in range(4)]
start = time.perf_counter()
for t in threads:
t.start()
for t in threads:
t.join()
elapsed = time.perf_counter() - start
print(f"Counter: {counter}") # Might not be 4,000,000!
print(f"Time: {elapsed:.2f}s") # About the SAME as single-threaded (GIL)
# For true parallelism, Python requires multiprocessing:
from multiprocessing import Pool
with Pool(4) as pool:
results = pool.map(cpu_work, data) # Separate processes, pickle overhead
Rust — True Parallelism, Compile-Time Safety
Rust:真正的并行与编译期安全
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let counter = Arc::new(AtomicI64::new(0));
let handles: Vec<_> = (0..4).map(|_| {
let counter = Arc::clone(&counter);
thread::spawn(move || {
for _ in 0..1_000_000 {
counter.fetch_add(1, Ordering::Relaxed);
}
})
}).collect();
for h in handles {
h.join().unwrap();
}
println!("Counter: {}", counter.load(Ordering::Relaxed)); // Always 4,000,000
// Runs on ALL cores — true parallelism, no GIL
}
Thread Safety: Type System Guarantees
线程安全:类型系统给出的保证
Python — Runtime Errors
Python:很多问题要到运行时才暴露
# Python — data races caught at runtime (or not at all)
import threading
shared_list = []
def append_items(items):
for item in items:
shared_list.append(item) # "Thread-safe" due to GIL for append
# But complex operations are NOT safe:
# if item not in shared_list:
# shared_list.append(item) # RACE CONDITION!
# Using Lock for safety:
lock = threading.Lock()
def safe_append(items):
for item in items:
with lock:
if item not in shared_list:
shared_list.append(item)
# Forgetting the lock? No compiler warning. Bug discovered in production.
Rust — Compile-Time Errors
Rust:很多错误编译不过去
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Trying to share a Vec across threads without protection:
// let shared = vec![];
// thread::spawn(move || shared.push(1));
// ❌ Compile error: Vec is not Send/Sync without protection
// With Mutex (Rust's equivalent of threading.Lock):
let shared = Arc::new(Mutex::new(Vec::new()));
let handles: Vec<_> = (0..4).map(|i| {
let shared = Arc::clone(&shared);
thread::spawn(move || {
let mut data = shared.lock().unwrap(); // Lock is REQUIRED to access
data.push(i);
// Lock is automatically released when `data` goes out of scope
// No "forgetting to unlock" — RAII guarantees it
})
}).collect();
for h in handles {
h.join().unwrap();
}
println!("{:?}", shared.lock().unwrap()); // [0, 1, 2, 3] (order may vary)
}
Send and Sync Traits
Send 与 Sync trait
#![allow(unused)]
fn main() {
// Rust uses two marker traits to enforce thread safety:
// Send — "this type can be transferred to another thread"
// Most types are Send. Rc<T> is NOT (use Arc<T> for threads).
// Sync — "this type can be referenced from multiple threads"
// Most types are Sync. Cell<T>/RefCell<T> are NOT (use Mutex<T>).
// The compiler checks these automatically:
// thread::spawn(move || { ... })
// ↑ The closure's captures must be Send
// ↑ Shared references must be Sync
// ↑ If they're not → compile error
// Python has no equivalent. Thread safety bugs are discovered at runtime.
// Rust catches them at compile time. This is "fearless concurrency."
}
Send 和 Sync 这俩名字第一次看挺抽象,实际上说的就是“能不能在线程之间移动”和“能不能在线程之间共享引用”。理解成这两句人话,基本就顺了。Send and Sync sound abstract at first, but they simply answer two questions: can this value move across threads, and can references to it be shared across threads?
Concurrency Primitives Comparison
并发原语对照
| Python | Rust | Purpose 用途 |
|---|---|---|
threading.Lock() | Mutex<T> | Mutual exclusion 互斥访问 |
threading.RLock() | Mutex<T> | Reentrant lock in Python; Rust usually models ownership differently Python 是可重入锁;Rust 通常换一种设计来避免这种需求 |
threading.RWLock (N/A) | RwLock<T> | Multiple readers or one writer 多读单写 |
threading.Event() | Condvar | Condition variable 条件变量 |
queue.Queue() | mpsc::channel() | Thread-safe message channel 线程安全消息通道 |
multiprocessing.Pool | rayon::ThreadPool | Thread pool 线程池 |
concurrent.futures | rayon / tokio::spawn | Task-based parallelism 基于任务的并行 |
threading.local() | thread_local! | Thread-local storage 线程局部存储 |
| N/A | Atomic* types | Lock-free counters and flags 无锁原子计数器与标志位 |
Mutex Poisoning
Mutex 中毒
If a thread panics while holding a Mutex, the lock becomes poisoned. Python has no direct equivalent. If a thread crashes while holding threading.Lock(), the program just gets stuck in a much uglier way.
如果某个线程在持有 Mutex 时 panic,这把锁就会进入 poisoned 状态。Python 没有这一层明确机制,线程拿着锁崩掉时,剩下的线程往往只会用更别扭的方式卡死在那里。
#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use std::thread;
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let data2 = Arc::clone(&data);
let _ = thread::spawn(move || {
let mut guard = data2.lock().unwrap();
guard.push(4);
panic!("oops!"); // Lock is now poisoned
}).join();
// Subsequent lock attempts return Err(PoisonError)
match data.lock() {
Ok(guard) => println!("Data: {guard:?}"),
Err(poisoned) => {
println!("Lock was poisoned! Recovering...");
let guard = poisoned.into_inner();
println!("Recovered: {guard:?}"); // [1, 2, 3, 4]
}
}
}
Atomic Ordering
原子操作中的内存序
The Ordering parameter on atomic operations controls memory visibility guarantees.
原子操作里的 Ordering 参数,控制的是内存可见性和执行顺序保证。
| Ordering | When to use 什么时候用 |
|---|---|
Relaxed | Simple counters where ordering does not matter 只关心计数值、不关心先后关系时 |
Acquire/Release | Producer-consumer handoff 生产者消费者交接数据时 |
SeqCst | When in doubt; strongest and easiest to reason about 拿不准就先用它,语义最强也最好理解 |
Python 的 threading 基本把这些细节都藏在 GIL 后面了。Rust 让人自己选,权力大,责任也大,所以拿不准时先用 SeqCst 往往比较稳。
Python largely hides these memory-ordering details behind the GIL. Rust exposes the choice, which is powerful but also demands care, so SeqCst is a good default when correctness matters more than micro-optimization.
async/await Comparison
async/await 对照
Python and Rust both use async/await syntax, but the runtimes and performance model underneath are very different.
Python 和 Rust 都有 async/await 语法,但底下的运行时模型和性能边界差别很大。
Python async/await
Python 的 async/await
# Python — asyncio for concurrent I/O
import asyncio
import aiohttp
async def fetch_url(session, url):
async with session.get(url) as resp:
return await resp.text()
async def main():
urls = ["https://example.com", "https://httpbin.org/get"]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, result in zip(urls, results):
print(f"{url}: {len(result)} bytes")
asyncio.run(main())
# Python async is single-threaded (still GIL)!
# It only helps with I/O-bound work (waiting for network/disk).
# CPU-bound work in async still blocks the event loop.
Rust async/await
Rust 的 async/await
// Rust — tokio for concurrent I/O (and CPU parallelism!)
use reqwest;
use tokio;
async fn fetch_url(url: &str) -> Result<String, reqwest::Error> {
reqwest::get(url).await?.text().await
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let urls = vec!["https://example.com", "https://httpbin.org/get"];
let tasks: Vec<_> = urls.iter()
.map(|url| tokio::spawn(fetch_url(url))) // No GIL limitation
.collect(); // Can use all CPU cores
let results = futures::future::join_all(tasks).await;
for (url, result) in urls.iter().zip(results) {
match result {
Ok(Ok(body)) => println!("{url}: {} bytes", body.len()),
Ok(Err(e)) => println!("{url}: error {e}"),
Err(e) => println!("{url}: task failed {e}"),
}
}
Ok(())
}
Key Differences
关键差异
| Aspect | Python asyncio | Rust tokio |
|---|---|---|
| GIL | Still applies 仍然存在 | No GIL 没有 GIL |
| CPU parallelism | ❌ Single-threaded 通常单线程 | ✅ Multi-threaded 可多线程并行 |
| Runtime | Built-in 内建 | External crate 外部 crate |
| Ecosystem | aiohttp, asyncpg | reqwest, sqlx |
| Performance | Good for I/O 适合 I/O | Excellent for I/O and capable around CPU orchestration I/O 很强,也更容易和并行任务结合 |
| Error handling | Exceptions 异常 | Result<T, E> |
| Cancellation | task.cancel() | Drop the future 丢弃 future |
| Color problem | Exists | Also exists |
Simple Parallelism with Rayon
用 Rayon 做简单并行
# Python — multiprocessing for CPU parallelism
from multiprocessing import Pool
def process_item(item):
return heavy_computation(item)
with Pool(8) as pool:
results = pool.map(process_item, items)
#![allow(unused)]
fn main() {
// Rust — rayon for effortless CPU parallelism (one line change!)
use rayon::prelude::*;
// Sequential:
let results: Vec<_> = items.iter().map(|item| heavy_computation(item)).collect();
// Parallel (change .iter() to .par_iter() — that's it!):
let results: Vec<_> = items.par_iter().map(|item| heavy_computation(item)).collect();
// No pickle, no process overhead, no serialization.
// Rayon automatically distributes work across cores.
}
Case Study: Parallel Image Processing Pipeline
案例:并行图像处理流水线
A data science team processes 50,000 satellite images nightly. Their Python pipeline uses multiprocessing.Pool.
某数据团队每晚要处理 5 万张卫星图像,原来的 Python 流水线靠 multiprocessing.Pool 顶着跑。
# Python — multiprocessing for CPU-bound image work
import multiprocessing
from PIL import Image
import numpy as np
def process_image(path: str) -> dict:
img = np.array(Image.open(path))
# CPU-intensive: histogram equalization, edge detection, classification
histogram = np.histogram(img, bins=256)[0]
edges = detect_edges(img) # ~200ms per image
label = classify(edges) # ~100ms per image
return {"path": path, "label": label, "edge_count": len(edges)}
# Problem: each subprocess copies the full Python interpreter
# Memory: 50MB per worker × 16 workers = 800MB overhead
# Startup: 2-3 seconds to fork and pickle arguments
with multiprocessing.Pool(16) as pool:
results = pool.map(process_image, image_paths) # ~4.5 hours for 50k images
Pain points: 800MB memory overhead from forking, pickle serialization of arguments and results, the GIL forcing process-based workarounds, and ugly debugging when workers fail.
主要痛点: 进程分叉带来 800MB 额外内存,参数和结果都要 pickle,GIL 逼着人走多进程,worker 出错时还特别难排查。
use rayon::prelude::*;
use image::GenericImageView;
struct ImageResult {
path: String,
label: String,
edge_count: usize,
}
fn process_image(path: &str) -> Result<ImageResult, image::ImageError> {
let img = image::open(path)?;
let histogram = compute_histogram(&img); // ~50ms (no numpy overhead)
let edges = detect_edges(&img); // ~40ms (SIMD-optimized)
let label = classify(&edges); // ~20ms
Ok(ImageResult {
path: path.to_string(),
label,
edge_count: edges.len(),
})
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let paths: Vec<String> = load_image_paths()?;
// Rayon automatically uses all CPU cores — no forking, no pickle, no GIL
let results: Vec<ImageResult> = paths
.par_iter() // Parallel iterator
.filter_map(|p| process_image(p).ok()) // Skip errors gracefully
.collect(); // Collect in parallel
println!("Processed {} images", results.len());
Ok(())
}
// 50k images in ~35 minutes (vs 4.5 hours in Python)
// Memory: ~50MB total (shared threads, no forking)
Results:
结果:
| Metric | Python | Rust |
|---|---|---|
| Time for 50k images 5 万张耗时 | ~4.5 hours | ~35 minutes |
| Memory overhead 额外内存 | 800MB | ~50MB |
| Error handling 错误处理 | Opaque worker exceptions 异常难查 | Result<T, E> everywhere每一步都有明确结果类型 |
| Startup cost 启动成本 | 2–3s | Near zero 几乎没有 |
Key lesson: For CPU-heavy parallel workloads, Rust threads plus Rayon avoid Python’s serialization overhead while keeping memory shared and correctness checked at compile time.
核心结论: 在 CPU 密集并行场景里,Rust 线程配合 Rayon 能把 Python 多进程那套序列化与内存开销省掉,同时还保留编译期正确性检查。
Exercises
练习
🏋️ Exercise: Thread-Safe Counter
🏋️ 练习:线程安全计数器
Challenge: In Python, a shared counter would often use threading.Lock. Translate that idea into Rust: spawn 10 threads, have each one increment the same counter 1000 times, and print the final value. Use Arc<Mutex<u64>>.
挑战:在 Python 里,这类共享计数器通常会配 threading.Lock。把它翻译成 Rust:启动 10 个线程,每个线程把同一个计数器加 1000 次,最后打印结果。要求使用 Arc<Mutex<u64>>。
🔑 Solution
🔑 参考答案
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0u64));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
handles.push(thread::spawn(move || {
for _ in 0..1000 {
let mut num = counter.lock().unwrap();
*num += 1;
}
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Final count: {}", *counter.lock().unwrap());
}
Key takeaway: Arc<Mutex<T>> is Rust’s equivalent of a shared value plus a lock, but the compiler forces the synchronization pattern into the type itself. That means fewer “forgot the lock” bugs escaping into production.
核心收获: Arc<Mutex<T>> 基本就是 Rust 里“共享数据 + 锁”的组合写法,但 Rust 会把同步要求直接编码进类型里,所以那种“锁忘了加,结果线上炸锅”的低级事故会少很多。
Unsafe Rust and FFI §§ZH§§ unsafe Rust 与 FFI
When and Why to Use Unsafe
什么时候该用 unsafe,为什么会需要它
What you’ll learn: What
unsafepermits and why it exists, writing Python extensions with PyO3, Rust’s testing framework vs pytest, mocking with mockall, and benchmarking basics.
本章将学习:unsafe允许做什么、它存在的原因、如何用 PyO3 编写 Python 扩展、Rust 测试框架与 pytest 的对应关系、如何用 mockall 做 mock,以及基准测试的基础思路。Difficulty: 🔴 Advanced
难度: 🔴 高级
unsafe in Rust is an escape hatch. It tells the compiler: “This part cannot be fully verified automatically, but the invariants are still my responsibility.” Python has no direct equivalent because Python never hands over raw memory access in the same way.
Rust 里的 unsafe 本质上是一扇逃生门,意思是:“这一小段编译器没法完全替着验证,但相关不变量依然要由代码作者负责。”Python 没有完全对应的东西,因为它本来就很少把原始内存访问权直接交出来。
flowchart TB
subgraph Safe ["Safe Rust (99% of code)<br/>安全 Rust"]
S1["Your application logic<br/>业务逻辑"]
S2["pub fn safe_api(&self) -> Result<br/>安全 API"]
end
subgraph Unsafe ["unsafe block (minimal, audited)<br/>最小化且可审计的 unsafe 块"]
U1["Raw pointer dereference<br/>裸指针解引用"]
U2["FFI call to C/Python<br/>调用 C 或 Python FFI"]
end
subgraph External ["External (C / Python / OS)<br/>外部系统"]
E1["libc / PyO3 / system calls"]
end
S1 --> S2
S2 --> U1
S2 --> U2
U1 --> E1
U2 --> E1
style Safe fill:#d4edda,stroke:#28a745
style Unsafe fill:#fff3cd,stroke:#ffc107
style External fill:#f8d7da,stroke:#dc3545
The pattern: a small
unsafeblock sits behind a safe API, and callers never have to touchunsafethemselves. Python’sctypesworld is much blurrier; every boundary call is effectively risky by default.
典型模式: 把一小段unsafe藏在安全 API 后面,调用方根本看不见unsafe。相比之下,Python 的ctypes边界通常更模糊,每一次跨边界调用都默认带着风险。📌 See also: Ch. 13 — Concurrency introduces
SendandSync, which areunsafeauto-traits checked by the compiler to keep threaded code sound.
📌 延伸阅读: 第 13 章——并发 里提到的Send和Sync,本质上也是和unsafe语义紧密相关的自动 trait,编译器会借它们保证并发代码的正确性。
What unsafe Allows
unsafe 允许做什么
// unsafe lets you do FIVE things that safe Rust forbids:
// 1. Dereference raw pointers
// 2. Call unsafe functions/methods
// 3. Access mutable static variables
// 4. Implement unsafe traits
// 5. Access union fields
// Example: calling a C function
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
// SAFETY: abs() is a well-defined C standard library function.
let result = unsafe { abs(-42) }; // Safe Rust can't verify C code
println!("{result}"); // 42
}
When to Use unsafe
什么时候该用 unsafe
#![allow(unused)]
fn main() {
// 1. FFI — calling C libraries (most common reason)
// 2. Performance-critical inner loops (rare)
// 3. Data structures the borrow checker can't express (rare)
// As a Python developer, you'll mostly encounter unsafe in:
// - PyO3 internals (Python ↔ Rust bridge)
// - C library bindings
// - Low-level system calls
// Rule of thumb: if you're writing application code (not library code),
// you should almost never need unsafe. If you think you do, ask in the
// Rust community first — there's usually a safe alternative.
}
日常应用代码里,unsafe 的出场率应该低得可怜。要是三天两头就想往里加 unsafe,多半是设计先跑偏了,不是编译器太严格。
In day-to-day application code, unsafe should be rare. If it starts showing up everywhere, that is usually a sign that the design needs rethinking rather than that Rust is “being too strict.”
PyO3: Rust Extensions for Python
PyO3:给 Python 写 Rust 扩展
PyO3 is the main bridge between Python and Rust. It lets Rust functions and types appear as ordinary Python-callable modules, which is exactly what many Python developers need when speeding up hot paths.
PyO3 是 Python 和 Rust 之间最常用的桥。它能让 Rust 函数和类型直接变成 Python 可调用模块,这对想给 Python 热点逻辑提速的人来说非常实用。
Creating a Python Extension in Rust
用 Rust 创建 Python 扩展
# Setup
pip install maturin # Build tool for Rust Python extensions
maturin init # Creates project structure
# Project structure:
# my_extension/
# ├── Cargo.toml
# ├── pyproject.toml
# └── src/
# └── lib.rs
# Cargo.toml
[package]
name = "my_extension"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # Shared library for Python
[dependencies]
pyo3 = { version = "0.22", features = ["extension-module"] }
#![allow(unused)]
fn main() {
// src/lib.rs — Rust functions callable from Python
use pyo3::prelude::*;
/// A fast Fibonacci function written in Rust.
#[pyfunction]
fn fibonacci(n: u64) -> u64 {
let (mut a, mut b) = (0u64, 1u64);
for _ in 0..n {
let temp = b;
b = a.wrapping_add(b);
a = temp;
}
a
}
/// Find all prime numbers up to n (Sieve of Eratosthenes).
#[pyfunction]
fn primes_up_to(n: usize) -> Vec<usize> {
let mut is_prime = vec![true; n + 1];
is_prime[0] = false;
if n > 0 { is_prime[1] = false; }
for i in 2..=((n as f64).sqrt() as usize) {
if is_prime[i] {
for j in (i * i..=n).step_by(i) {
is_prime[j] = false;
}
}
}
(2..=n).filter(|&i| is_prime[i]).collect()
}
/// A Rust class usable from Python.
#[pyclass]
struct Counter {
value: i64,
}
#[pymethods]
impl Counter {
#[new]
fn new(start: i64) -> Self {
Counter { value: start }
}
fn increment(&mut self) {
self.value += 1;
}
fn get_value(&self) -> i64 {
self.value
}
fn __repr__(&self) -> String {
format!("Counter(value={})", self.value)
}
}
/// The Python module definition.
#[pymodule]
fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(fibonacci, m)?)?;
m.add_function(wrap_pyfunction!(primes_up_to, m)?)?;
m.add_class::<Counter>()?;
Ok(())
}
}
Using from Python
在 Python 里使用
# Build and install:
maturin develop --release # Builds and installs into current venv
# Python — use the Rust extension like any Python module
import my_extension
# Call Rust function
result = my_extension.fibonacci(50)
print(result) # 12586269025 — computed in microseconds
# Use Rust class
counter = my_extension.Counter(0)
counter.increment()
counter.increment()
print(counter.get_value()) # 2
print(counter) # Counter(value=2)
# Performance comparison:
import time
# Python version
def py_primes(n):
sieve = [True] * (n + 1)
for i in range(2, int(n**0.5) + 1):
if sieve[i]:
for j in range(i*i, n+1, i):
sieve[j] = False
return [i for i in range(2, n+1) if sieve[i]]
start = time.perf_counter()
py_result = py_primes(10_000_000)
py_time = time.perf_counter() - start
start = time.perf_counter()
rs_result = my_extension.primes_up_to(10_000_000)
rs_time = time.perf_counter() - start
print(f"Python: {py_time:.3f}s") # ~3.5s
print(f"Rust: {rs_time:.3f}s") # ~0.05s — 70x faster!
print(f"Same results: {py_result == rs_result}") # True
PyO3 Quick Reference
PyO3 速查表
| Python Concept | PyO3 Attribute | Notes 说明 |
|---|---|---|
| Function | #[pyfunction] | Exposed to Python 暴露给 Python |
| Class | #[pyclass] | Python-visible class Python 可见类 |
| Method | #[pymethods] | Methods on a pyclass 类方法集合 |
__init__ | #[new] | Constructor 构造函数 |
__repr__ | fn __repr__() | String representation 调试字符串表示 |
__str__ | fn __str__() | Display string 显示字符串 |
__len__ | fn __len__() | Length 长度 |
__getitem__ | fn __getitem__() | Indexing 下标访问 |
| Property | #[getter] / #[setter] | Attribute access 属性访问 |
| Static method | #[staticmethod] | No self 无 self |
| Class method | #[classmethod] | Takes cls接收类对象 |
FFI Safety Patterns
FFI 安全模式
When exposing Rust to Python or C, these rules avoid the most common disasters.
当 Rust 要暴露给 Python 或 C 使用时,下面这些规则能挡住最常见的爆炸现场。
- Never let a panic cross the FFI boundary. PyO3 handles this for
#[pyfunction], but rawextern "C"functions must catch panics manually.
#![allow(unused)]
fn main() {
#[no_mangle]
pub extern "C" fn raw_ffi_function() -> i32 {
match std::panic::catch_unwind(|| {
// actual logic
42
}) {
Ok(result) => result,
Err(_) => -1, // Return error code instead of panicking into C/Python
}
}
}
1. 绝对别让 panic 穿过 FFI 边界。 #[pyfunction] 这类接口 PyO3 会帮着兜住,但裸写 extern "C" 时,必须手动 catch_unwind。
- Use
#[repr(C)]for shared structs when another language reads the fields directly. Otherwise the内存布局就没有稳定保证。
2. 跨语言共享字段布局时,一定加 #[repr(C)]。 如果对方语言要直接按字段读结构体,没这个注解就别指望布局稳定。
- Use
extern "C"for raw FFI functions so the calling convention matches. PyO3 hides this for Python-facing functions.
3. 原始 FFI 函数要用 extern "C"。 这样调用约定才一致。PyO3 对面向 Python 的函数会把这些底层细节包起来。
PyO3 advantage: PyO3 automatically handles panic conversion, type marshalling, and much of the GIL interaction. Unless there is a very specific low-level requirement, it is far better than hand-rolled raw FFI.
PyO3 的优势: 它会替着处理 panic 转换、类型转换和大量 GIL 相关细节。除非真有非常底层的特殊要求,否则一般都比手搓裸 FFI 舒服得多。
Unit Tests vs pytest
单元测试与 pytest 对照
Python Testing with pytest
Python 里用 pytest
# test_calculator.py
import pytest
from calculator import add, divide
def test_add():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, 1) == 0
def test_divide():
assert divide(10, 2) == 5.0
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(1, 0)
# Parameterized tests
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, -1, -2),
(100, 200, 300),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected
# Fixtures
@pytest.fixture
def sample_data():
return [1, 2, 3, 4, 5]
def test_sum(sample_data):
assert sum(sample_data) == 15
# Running tests
pytest # Run all tests
pytest test_calculator.py # Run one file
pytest -k "test_add" # Run matching tests
pytest -v # Verbose output
pytest --tb=short # Short tracebacks
Rust Built-in Testing
Rust 内建测试框架
#![allow(unused)]
fn main() {
// src/calculator.rs — tests live in the SAME file!
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
// Tests go in a #[cfg(test)] module — only compiled during `cargo test`
#[cfg(test)]
mod tests {
use super::*; // Import everything from parent module
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide() {
assert_eq!(divide(10.0, 2.0), Ok(5.0));
}
#[test]
fn test_divide_by_zero() {
assert!(divide(1.0, 0.0).is_err());
}
// Test that something panics (like pytest.raises)
#[test]
#[should_panic(expected = "out of bounds")]
fn test_out_of_bounds() {
let v = vec![1, 2, 3];
let _ = v[99]; // Panics
}
}
}
# Running tests
cargo test # Run all tests
cargo test test_add # Run matching tests
cargo test -- --nocapture # Show println! output
cargo test -p my_crate # Test one crate in workspace
cargo test -- --test-threads=1 # Sequential (for tests with side effects)
Testing Quick Reference
测试速查表
| pytest | Rust | Notes 说明 |
|---|---|---|
assert x == y | assert_eq!(x, y) | Equality 相等断言 |
assert x != y | assert_ne!(x, y) | Inequality 不相等断言 |
assert condition | assert!(condition) | Boolean check 布尔条件断言 |
assert condition, "msg" | assert!(condition, "msg") | With message 带消息 |
pytest.raises(E) | #[should_panic] | Expect panic 预期 panic |
@pytest.fixture | Helper setup code | No built-in fixture system 标准库没有完全对等的 fixture 机制 |
@pytest.mark.parametrize | rstest crate | Parameterized tests 参数化测试 |
conftest.py | tests/common/mod.rs | Shared helpers 共享测试辅助 |
pytest.skip() | #[ignore] | Skip a test 跳过测试 |
tmp_path fixture | tempfile crate | Temporary directories 临时目录 |
Parameterized Tests with rstest
用 rstest 写参数化测试
#![allow(unused)]
fn main() {
// Cargo.toml: rstest = "0.23"
use rstest::rstest;
// Like @pytest.mark.parametrize
#[rstest]
#[case(1, 2, 3)]
#[case(0, 0, 0)]
#[case(-1, -1, -2)]
#[case(100, 200, 300)]
fn test_add(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
assert_eq!(add(a, b), expected);
}
// Like @pytest.fixture
use rstest::fixture;
#[fixture]
fn sample_data() -> Vec<i32> {
vec![1, 2, 3, 4, 5]
}
#[rstest]
fn test_sum(sample_data: Vec<i32>) {
assert_eq!(sample_data.iter().sum::<i32>(), 15);
}
}
Mocking with mockall
用 mockall 做 mock
# Python — mocking with unittest.mock
from unittest.mock import Mock, patch
def test_fetch_user():
mock_db = Mock()
mock_db.get_user.return_value = {"name": "Alice"}
result = fetch_user_name(mock_db, 1)
assert result == "Alice"
mock_db.get_user.assert_called_once_with(1)
#![allow(unused)]
fn main() {
// Rust — mocking with mockall crate
// Cargo.toml: mockall = "0.13"
use mockall::{automock, predicate::*};
#[automock] // Generates MockDatabase automatically
trait Database {
fn get_user(&self, id: i64) -> Option<User>;
}
fn fetch_user_name(db: &dyn Database, id: i64) -> Option<String> {
db.get_user(id).map(|u| u.name)
}
#[test]
fn test_fetch_user() {
let mut mock = MockDatabase::new();
mock.expect_get_user()
.with(eq(1)) // assert_called_with(1)
.times(1) // assert_called_once
.returning(|_| Some(User { name: "Alice".into() }));
let result = fetch_user_name(&mock, 1);
assert_eq!(result, Some("Alice".to_string()));
}
}
mockall 和 Python 的 unittest.mock 不同,它不是到处临时打补丁,而是鼓励先把依赖抽象成 trait,再对 trait 生成 mock。麻烦一点,但结构更健康。
mockall works differently from Python’s unittest.mock. Instead of patching symbols all over the place, it encourages modeling dependencies as traits and generating mocks from those interfaces, which is more explicit and usually healthier for the design.
Exercises
练习
🏋️ Exercise: Safe Wrapper Around Unsafe
🏋️ 练习:给 `unsafe` 套一层安全包装
Challenge: Write a safe function split_at_mid that takes a &mut [i32] and returns two mutable slices split at the midpoint. Internally, use raw pointers and unsafe, similar to how split_at_mut works, but present a safe API to callers.
挑战:写一个安全函数 split_at_mid,接收 &mut [i32] 并在中点拆成两个可变切片返回。内部允许使用裸指针和 unsafe,模仿 split_at_mut 的实现思路,但对调用方暴露的接口必须是安全的。
🔑 Solution
🔑 参考答案
fn split_at_mid(slice: &mut [i32]) -> (&mut [i32], &mut [i32]) {
let mid = slice.len() / 2;
let ptr = slice.as_mut_ptr();
let len = slice.len();
assert!(mid <= len); // Safety check before unsafe
unsafe {
// SAFETY: mid <= len (asserted above), and ptr comes from a valid &mut slice,
// so both sub-slices are within bounds and non-overlapping.
(
std::slice::from_raw_parts_mut(ptr, mid),
std::slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
fn main() {
let mut data = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mid(&mut data);
left[0] = 99;
right[0] = 88;
println!("left: {left:?}, right: {right:?}");
// left: [99, 2, 3], right: [88, 5, 6]
}
Key takeaway: Keep the unsafe block tiny, guard it with explicit checks, and expose only a safe interface outward. That is the standard Rust pattern: unsafe internals, safe public API.
核心收获: unsafe 块要尽量小,前面要有明确校验,对外只暴露安全接口。这就是 Rust 处理不安全代码的标准套路:内部不安全,外部安全。
Migration Patterns §§ZH§§ 迁移模式
Common Python Patterns in Rust
Rust 中对应的常见 Python 模式
What you’ll learn: How to translate
dictintostruct,classintostruct + impl, list comprehensions into iterator chains, decorators into traits or wrappers, and context managers intoDrop/ RAII. Also included: essential crates and an incremental migration strategy.
本章将学习: 如何把dict迁移成struct,把class迁移成struct + impl,把列表推导式迁移成迭代器链,把装饰器迁移成 trait 或包装函数,以及把上下文管理器迁移成Drop/ RAII。同时还会补充常用 crate 和渐进式迁移策略。Difficulty: 🟡 Intermediate
难度: 🟡 进阶
Dictionary → Struct
字典 → 结构体
# Python — dict as data container (very common)
user = {
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"active": True,
}
print(user["name"])
#![allow(unused)]
fn main() {
// Rust — struct with named fields
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct User {
name: String,
age: i32,
email: String,
active: bool,
}
let user = User {
name: "Alice".into(),
age: 30,
email: "alice@example.com".into(),
active: true,
};
println!("{}", user.name);
}
When Python code uses dictionaries as “free-form data bags”, Rust usually wants a named struct instead. That looks stricter, but it pays off in discoverability, tooling, and compile-time validation.
当 Python 把字典当成“随手塞字段的数据袋”时,Rust 一般更倾向于用具名结构体来承载。这样确实更严格,但换来的是更强的可读性、工具支持和编译期校验。
Context Manager → RAII (Drop)
上下文管理器 → RAII(Drop)
# Python — context manager for resource cleanup
class FileManager:
def __init__(self, path):
self.file = open(path, 'w')
def __enter__(self):
return self.file
def __exit__(self, *args):
self.file.close()
with FileManager("output.txt") as f:
f.write("hello")
#![allow(unused)]
fn main() {
// Rust — RAII: Drop trait runs when value goes out of scope
use std::fs::File;
use std::io::Write;
fn write_file() -> std::io::Result<()> {
let mut file = File::create("output.txt")?;
file.write_all(b"hello")?;
Ok(())
// File closes automatically when `file` leaves scope
}
}
Rust relies on scope-based cleanup rather than explicit with syntax. Once ownership leaves scope, the resource is cleaned up deterministically.
Rust 靠的是基于作用域的资源回收,而不是单独的 with 语法。所有者一旦离开作用域,资源就会按确定顺序被清理。
Decorator → Higher-Order Function or Macro
装饰器 → 高阶函数或宏
# Python — decorator for timing
import functools, time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timed
def slow_function():
time.sleep(1)
#![allow(unused)]
fn main() {
// Rust — no decorators, use wrapper functions or macros
use std::time::Instant;
fn timed<F, R>(name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let start = Instant::now();
let result = f();
println!("{} took {:.4?}", name, start.elapsed());
result
}
let result = timed("slow_function", || {
std::thread::sleep(std::time::Duration::from_secs(1));
42
});
}
Rust has no direct decorator syntax, but wrapper functions, closures, traits, and macros usually cover the same design space in a more explicit way.
Rust 没有和装饰器完全一模一样的语法,但包装函数、闭包、trait、宏这些组合起来,通常能覆盖同样的设计需求,只是写法更显式。
Iterator Pipeline (Data Processing)
迭代器流水线(数据处理)
# Python — chain of transformations
import csv
from collections import Counter
def analyze_sales(filename):
with open(filename) as f:
reader = csv.DictReader(f)
sales = [
row for row in reader
if float(row["amount"]) > 100
]
by_region = Counter(sale["region"] for sale in sales)
top_regions = by_region.most_common(5)
return top_regions
#![allow(unused)]
fn main() {
// Rust — iterator chains with strong types
use std::collections::HashMap;
#[derive(Debug, serde::Deserialize)]
struct Sale {
region: String,
amount: f64,
}
fn analyze_sales(filename: &str) -> Vec<(String, usize)> {
let data = std::fs::read_to_string(filename).unwrap();
let mut reader = csv::Reader::from_reader(data.as_bytes());
let mut by_region: HashMap<String, usize> = HashMap::new();
for sale in reader.deserialize::<Sale>().flatten() {
if sale.amount > 100.0 {
*by_region.entry(sale.region).or_insert(0) += 1;
}
}
let mut top: Vec<_> = by_region.into_iter().collect();
top.sort_by(|a, b| b.1.cmp(&a.1));
top.truncate(5);
top
}
}
Global Config / Singleton
全局配置 / 单例
# Python — module-level singleton
import json
class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
with open("config.json") as f:
cls._instance.data = json.load(f)
return cls._instance
config = Config()
#![allow(unused)]
fn main() {
// Rust — OnceLock for lazy static initialization
use std::sync::OnceLock;
use serde_json::Value;
static CONFIG: OnceLock<Value> = OnceLock::new();
fn get_config() -> &'static Value {
CONFIG.get_or_init(|| {
let data = std::fs::read_to_string("config.json")
.expect("Failed to read config");
serde_json::from_str(&data)
.expect("Failed to parse config")
})
}
let db_host = get_config()["database"]["host"].as_str().unwrap();
}
Essential Crates for Python Developers
适合 Python 开发者优先认识的 crate
Data Processing & Serialization
数据处理与序列化
| Task 任务 | Python | Rust Crate | Notes 说明 |
|---|---|---|---|
| JSON | json | serde_json | Type-safe serialization 类型安全的序列化 |
| CSV | csv, pandas | csv | Streaming, low memory 支持流式处理,内存占用低 |
| YAML | pyyaml | serde_yaml | Config files |
| TOML | tomllib | toml | Config files |
| Data validation | pydantic | serde + custom | Compile-time + explicit validation 编译期约束配合显式校验 |
| Date/time | datetime | chrono | Full timezone support |
| Regex | re | regex | Very fast |
| UUID | uuid | uuid | Same concept |
Web & Network
Web 与网络
| Task 任务 | Python | Rust Crate | Notes 说明 |
|---|---|---|---|
| HTTP client | requests | reqwest | Async-first 异步优先 |
| Web framework | FastAPI / Flask | axum / actix-web | Very fast |
| WebSocket | websockets | tokio-tungstenite | Async |
| gRPC | grpcio | tonic | Full support |
| Database (SQL) | sqlalchemy | sqlx / diesel | Compile-time checked SQL SQL 约束更强 |
| Redis | redis-py | redis | Async support |
CLI & System
命令行与系统工具
| Task 任务 | Python | Rust Crate | Notes 说明 |
|---|---|---|---|
| CLI args | argparse / click | clap | Derive macros |
| Colored output | colorama | colored | Terminal colors |
| Progress bar | tqdm | indicatif | Similar UX |
| File watching | watchdog | notify | Cross-platform |
| Logging | logging | tracing | Structured and async-friendly |
| Env vars | os.environ | std::env + dotenvy | .env support |
| Subprocess | subprocess | std::process::Command | Built-in |
| Temp files | tempfile | tempfile | Same name |
Testing
测试
| Task 任务 | Python | Rust Crate | Notes 说明 |
|---|---|---|---|
| Test framework | pytest | Built-in + rstest | cargo test |
| Mocking | unittest.mock | mockall | Trait-based |
| Property testing | hypothesis | proptest | Similar idea |
| Snapshot testing | syrupy | insta | Snapshot approval |
| Benchmarking | pytest-benchmark | criterion | Statistical approach |
| Code coverage | coverage.py | cargo-tarpaulin | LLVM-based |
Incremental Adoption Strategy
渐进式引入策略
flowchart LR
A["1️⃣ Profile Python<br/>先找热点"] --> B["2️⃣ Write Rust Extension<br/>PyO3 + maturin"]
B --> C["3️⃣ Replace Python Call<br/>替换调用点"]
C --> D["4️⃣ Expand Gradually<br/>逐步扩大范围"]
D --> E{"Full rewrite worth it?<br/>值得全量重写吗?"}
E -->|Yes| F["Pure Rust 🦀"]
E -->|No| G["Hybrid 🐍 + 🦀"]
style A fill:#ffeeba
style B fill:#fff3cd
style C fill:#d4edda
style D fill:#d4edda
style F fill:#c3e6cb
style G fill:#c3e6cb
📌 See also: Ch. 14 — Unsafe Rust and FFI for the lower-level details behind PyO3 bindings.
📌 延伸阅读: 第 14 章——Unsafe Rust 与 FFI 会进一步讲 PyO3 绑定背后的底层细节。
Step 1: Identify Hotspots
第一步:先找热点
import cProfile
cProfile.run('main()')
# Or use py-spy:
# py-spy top --pid <python-pid>
# py-spy record -o profile.svg -- python main.py
Step 2: Write Rust Extension for the Hotspot
第二步:给热点写 Rust 扩展
cd my_python_project
maturin init --bindings pyo3
maturin develop --release
Step 3: Replace the Python Call
第三步:替换 Python 调用点
# Before:
result = python_hot_function(data)
# After:
import my_rust_extension
result = my_rust_extension.hot_function(data)
Step 4: Expand Gradually
第四步:逐步扩大范围
Week 1-2: Replace one CPU-bound function with Rust
Week 3-4: Replace data parsing/validation layer
Month 2: Replace core data pipeline
Month 3+: Consider full Rust rewrite if benefits justify it
Key principle: keep Python for orchestration, use Rust for computation.
第 1 到 2 周:先替换一个 CPU 密集函数第 3 到 4 周:再替换数据解析或校验层
第 2 个月:开始替换核心数据流水线
第 3 个月以后:如果收益足够大,再考虑完整重写
核心原则:Python 继续负责编排与胶水逻辑,Rust 专注高价值计算热点。
💼 Case Study: Accelerating a Data Pipeline with PyO3
案例:用 PyO3 加速数据流水线
A fintech startup processes 2GB of transaction CSV data every day. The slowest part is validation plus transformation.
一个金融科技团队每天要处理 2GB 交易 CSV。最慢的部分是校验和转换逻辑。
# Python — the slow part (~12 minutes for 2GB)
import csv
from decimal import Decimal
from datetime import datetime
def validate_and_transform(filepath: str) -> list[dict]:
results = []
with open(filepath) as f:
reader = csv.DictReader(f)
for row in reader:
amount = Decimal(row["amount"])
if amount < 0:
raise ValueError(f"Negative amount: {amount}")
date = datetime.strptime(row["date"], "%Y-%m-%d")
category = categorize(row["merchant"])
results.append({
"amount_cents": int(amount * 100),
"date": date.isoformat(),
"category": category,
"merchant": row["merchant"].strip().lower(),
})
return results
Step 1: Profile first and confirm that CSV parsing, decimal conversion, and string matching dominate runtime.
第一步:先 profile,确认耗时主要集中在 CSV 解析、金额转换和字符串匹配上。
Step 2: Move the hotspot into a Rust extension.
第二步:把热点逻辑搬进 Rust 扩展。
#![allow(unused)]
fn main() {
// src/lib.rs — PyO3 extension
use pyo3::prelude::*;
use pyo3::types::PyList;
use std::fs::File;
use std::io::BufReader;
#[derive(Debug)]
struct Transaction {
amount_cents: i64,
date: String,
category: String,
merchant: String,
}
fn categorize(merchant: &str) -> &'static str {
if merchant.contains("amazon") { "shopping" }
else if merchant.contains("uber") || merchant.contains("lyft") { "transport" }
else if merchant.contains("starbucks") { "food" }
else { "other" }
}
#[pyfunction]
fn process_transactions(path: &str) -> PyResult<Vec<(i64, String, String, String)>> {
let file = File::open(path).map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?;
let mut reader = csv::Reader::from_reader(BufReader::new(file));
let mut results = Vec::with_capacity(15_000_000);
for record in reader.records() {
let record = record.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
let amount_str = &record[0];
let amount_cents = parse_amount_cents(amount_str)?;
let date = &record[1];
let merchant = record[2].trim().to_lowercase();
let category = categorize(&merchant).to_string();
results.push((amount_cents, date.to_string(), category, merchant));
}
Ok(results)
}
#[pymodule]
fn fast_pipeline(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(process_transactions, m)?)?;
Ok(())
}
}
Step 3: Replace one call in Python and keep everything else the same.
第三步:只换掉 Python 里一个调用点,其余逻辑保持原样。
# Before:
results = validate_and_transform("transactions.csv")
# After:
import fast_pipeline
results = fast_pipeline.process_transactions("transactions.csv")
Results:
结果:
| Metric 指标 | Python | Rust |
|---|---|---|
| Time (2GB / 15M rows) 耗时 | 12 minutes | 45 seconds |
| Peak memory 峰值内存 | 6GB / 2GB | 200MB |
| Lines changed in Python Python 改动行数 | — | 1 |
| Rust code written 新增 Rust 代码 | — | ~60 lines |
| Tests passing 测试通过 | 47/47 | 47/47 |
Key lesson: Most teams do not need a full rewrite. Replacing the small fraction of code that consumes most of the runtime often captures most of the practical benefit.
关键教训:大多数团队根本不需要全量重写。只替换那一小部分最耗时的代码,往往就已经吃到了绝大多数收益。
Exercises
练习
🏋️ Exercise: Migration Decision Matrix
🏋️ 练习:迁移决策矩阵
Challenge: For each component of a Python web application below, decide whether it should stay in Python, move to Rust, or be bridged through PyO3. Give a short reason.
挑战:下面列出的是一个 Python Web 应用的不同组件。分别判断它们应该保留在 Python、重写成 Rust,还是通过 PyO3 桥接,并简要说明理由。
- Flask route handlers
1. Flask 路由处理器 - Image thumbnail generation
2. 图片缩略图生成 - SQLAlchemy ORM queries
3. SQLAlchemy ORM 查询 - Nightly CSV parsing for 2GB financial files
4. 每晚解析 2GB 金融 CSV - Admin dashboard templates
5. 管理后台模板页
🔑 Solution
🔑 参考答案
| Component 组件 | Decision 决策 | Rationale 理由 |
|---|---|---|
| Flask route handlers | 🐍 Keep Python | I/O-bound, framework-heavy, low performance return 偏 I/O、强框架绑定,迁移收益通常不高 |
| Image thumbnail generation | 🦀 PyO3 bridge | CPU-bound hotspot with clear boundary CPU 热点明确,边界清晰,很适合桥接 |
| Database ORM queries | 🐍 Keep Python | ORM 生态成熟,而且主要是 I/O 等待 成熟 ORM 生态优势明显,且核心瓶颈通常不是 CPU |
| CSV parser (2GB) | 🦀 PyO3 bridge or full Rust | CPU + memory sensitive, Rust parsing shines 既吃 CPU 又吃内存,Rust 在这里非常强 |
| Admin dashboard | 🐍 Keep Python | Mostly UI and templates, little execution pressure 主要是界面和模板逻辑,执行性能不是重点 |
Key takeaway: The best migration targets are usually CPU-heavy, performance-sensitive components with clean interfaces. Glue code and framework-heavy request handlers often stay more economical in Python.
核心收获:最值得迁移的部分,通常是 CPU 密集、性能敏感、边界清晰的组件。至于胶水代码和强框架绑定的请求处理逻辑,继续留在 Python 往往更划算。
Best Practices §§ZH§§ 最佳实践
Idiomatic Rust for Python Developers
面向 Python 开发者的地道 Rust 写法
What you’ll learn: Top 10 habits to build, common pitfalls with fixes, a structured 3-month learning path, the complete Python→Rust “Rosetta Stone” reference table, and recommended learning resources.
本章将学习: 需要尽快养成的 10 个习惯、常见误区及修正方法、为期 3 个月的学习路线、完整的 Python→Rust 对照表,以及推荐学习资源。Difficulty: 🟡 Intermediate
难度: 🟡 中级
flowchart LR
A["🟢 Week 1-2<br/>Foundations<br/>基础<br/>'Why won't this compile?'<br/>“为什么这都编不过?”"] --> B["🟡 Week 3-4<br/>Core Concepts<br/>核心概念<br/>'Oh, it's protecting me'<br/>“哦,原来它是在保护我”"]
B --> C["🟡 Month 2<br/>Intermediate<br/>进阶阶段<br/>'I see why this matters'<br/>“明白这东西为什么重要了”"]
C --> D["🔴 Month 3+<br/>Advanced<br/>高级阶段<br/>'Caught a bug at compile time!'<br/>“编译期就抓到 bug 了!”"]
D --> E["🏆 Month 6<br/>Fluent<br/>熟练掌握<br/>'Better programmer everywhere'<br/>“写什么语言都更顺手了”"]
style A fill:#d4edda
style B fill:#fff3cd
style C fill:#fff3cd
style D fill:#f8d7da
style E fill:#c3e6cb,stroke:#28a745
Top 10 Habits to Build
最该尽快养成的 10 个习惯
-
Use
matchon enums instead ofif isinstance()
遇到枚举时优先用match,别再沿用if isinstance()的脑回路。# Python # Rust if isinstance(shape, Circle): ... match shape { Shape::Circle(r) => ... } -
Let the compiler guide you — Read error messages carefully. Rust’s compiler is among the best available: it explains both the problem and the likely fix.
让编译器带路。 认真读错误信息。Rust 编译器给出的信息质量极高,通常既指出问题,也提示修正方向。 -
Prefer
&stroverStringin function parameters — Accept the most general string view when ownership is unnecessary.&strcovers bothStringand string literals.
函数参数能写&str就尽量别写String。 如果函数并不需要拿走所有权,就接收更通用的字符串视图。&str同时兼容String和字符串字面量。 -
Use iterators instead of index loops — Iterator chains are more idiomatic and often faster than
for i in 0..vec.len().
能用迭代器就别写索引循环。 迭代器链更符合 Rust 风格,很多时候也比for i in 0..vec.len()更高效。 -
Embrace
OptionandResult— Avoid.unwrap()everywhere. Reach for?,map,and_then, andunwrap_or_else.
接受Option和Result这套表达方式。 别什么都.unwrap(),多用?、map、and_then、unwrap_or_else。 -
Derive traits liberally —
#[derive(Debug, Clone, PartialEq)]belongs on many structs. It costs little and helps debugging and testing.
常见 trait 该派生就派生。 很多结构体都适合带上#[derive(Debug, Clone, PartialEq)],成本很低,对调试和测试很有帮助。 -
Use
cargo clippyreligiously — Treat it likerufffor Rust. It catches a surprising number of style and correctness issues.
把cargo clippy当成日常动作。 可以把它理解成 Rust 版的ruff,能提前指出大量风格和正确性问题。 -
Don’t fight the borrow checker — If it keeps pushing back, the data layout or ownership flow likely needs refactoring.
别和借用检查器硬顶。 如果它一直报错,通常说明数据组织方式或所有权流向还需要重新整理。 -
Use enums for state machines — Replace string flags and scattered booleans with enums so the compiler can enforce all states.
状态机优先建模成枚举。 用enum代替字符串标记和零散布尔值,让编译器帮忙覆盖所有状态。 -
Clone first, optimize later — During the learning stage, using
.clone()selectively can keep ownership complexity manageable. Optimize only when profiling proves it matters.
先学会,再抠性能。 刚入门时适当使用.clone()没问题,先把所有权规则摸顺;只有在性能分析明确表明必要时,再回头压缩克隆次数。
Common Mistakes from Python Developers
Python 开发者最常见的误区
| Mistake 误区 | Why 原因 | Fix 修正方式 |
|---|---|---|
.unwrap() everywhere到处乱用 .unwrap() | Panics at runtime 运行时可能直接 panic | Use ? or match改用 ? 或 match |
String instead of &str参数一上来就写 String | Unnecessary allocation 引入额外分配 | Use &str for params参数优先写 &str |
for i in 0..vec.len()执着于下标循环 | Not idiomatic 不符合 Rust 常用写法 | for item in &vec改成 for item in &vec |
| Ignoring clippy warnings 无视 clippy 警告 | Miss easy improvements 错过很多明显改进点 | Run cargo clippy坚持跑 cargo clippy |
Too many .clone() calls.clone() 用太多 | Performance overhead 带来性能负担 | Refactor ownership 重构所有权设计 |
Giant main() functionmain() 写成大杂烩 | Hard to test 测试困难 | Extract into lib.rs把逻辑提到 lib.rs |
Not using #[derive()]重复造常见 trait 的轮子 | Re-inventing the wheel 白白增加样板代码 | Derive common traits 直接派生常见 trait |
| Panicking on errors 一出错就 panic | Not recoverable 调用方无法恢复 | Return Result<T, E>改为返回 Result<T, E> |
Performance Comparison
性能对比
Benchmark: Common Operations
基准测试:常见操作
Operation Python 3.12 Rust (release) Speedup
───────────────────── ──────────── ────────────── ─────────
Fibonacci(40) ~25s ~0.3s ~80x
Sort 10M integers ~5.2s ~0.6s ~9x
JSON parse 100MB ~8.5s ~0.4s ~21x
Regex 1M matches ~3.1s ~0.3s ~10x
HTTP server (req/s) ~5,000 ~150,000 ~30x
SHA-256 1GB file ~12s ~1.2s ~10x
CSV parse 1M rows ~4.5s ~0.2s ~22x
String concatenation ~2.1s ~0.05s ~42x
操作 Python 3.12 Rust(release) 提升倍数───────────────────── ──────────── ────────────── ────────
Fibonacci(40) 约 25 秒 约 0.3 秒 约 80 倍
排序 1000 万整数 约 5.2 秒 约 0.6 秒 约 9 倍
解析 100MB JSON 约 8.5 秒 约 0.4 秒 约 21 倍
正则匹配 100 万次 约 3.1 秒 约 0.3 秒 约 10 倍
HTTP 服务器吞吐量 约 5,000 req/s 约 150,000 req/s 约 30 倍
SHA-256 处理 1GB 文件 约 12 秒 约 1.2 秒 约 10 倍
CSV 解析 100 万行 约 4.5 秒 约 0.2 秒 约 22 倍
字符串拼接 约 2.1 秒 约 0.05 秒 约 42 倍
Note: Python with C extensions such as NumPy can narrow the gap dramatically for numerical work. The table above compares pure Python against pure Rust.
说明:如果 Python 侧使用 NumPy 这类 C 扩展,数值计算上的差距会被大幅缩小。这里的对比主要针对“纯 Python”和“纯 Rust”。
Memory Usage
内存占用
Python: Rust:
───────── ─────
- Object header: 28 bytes/object - No object header
- int: 28 bytes (even for 0) - i32: 4 bytes, i64: 8 bytes
- str "hello": 54 bytes - &str "hello": 16 bytes (ptr + len)
- list of 1000 ints: ~36 KB - Vec<i32>: ~4 KB
(8 KB pointers + 28 KB int objects)
- dict of 100 items: ~5.5 KB - HashMap of 100: ~2.4 KB
Total for typical application:
- Python: 50-200 MB baseline - Rust: 1-5 MB baseline
Python:- 每个对象通常带约 28 字节对象头
- `int` 即使是 0,也常常要占约 28 字节
- 字符串 `"hello"` 约 54 字节
- 1000 个整数的列表约 36 KB,其中包含指针和对象本身开销
- 100 项字典约 5.5 KB
Rust:
- 没有统一对象头
- `i32` 为 4 字节,`i64` 为 8 字节
- `&str "hello"` 约 16 字节,存的是指针和长度
- `Vec<i32>` 装 1000 个整数约 4 KB
- 100 项 `HashMap` 约 2.4 KB
典型应用的基线内存:Python 常见在 50 到 200 MB,Rust 常见在 1 到 5 MB。
Common Pitfalls and Solutions
常见坑点与修正方式
Pitfall 1: “The Borrow Checker Won’t Let Me”
坑点 1:“借用检查器又不让过”
#![allow(unused)]
fn main() {
// Problem: trying to iterate and modify
let mut items = vec![1, 2, 3, 4, 5];
// for item in &items {
// if *item > 3 { items.push(*item * 2); } // ❌ Can't borrow mut while borrowed
// }
// Solution 1: collect changes, apply after
let additions: Vec<i32> = items.iter()
.filter(|&&x| x > 3)
.map(|&x| x * 2)
.collect();
items.extend(additions);
// Solution 2: use retain/extend
items.retain(|&x| x <= 3);
}
The issue is not that Rust is being difficult for no reason. The problem is simultaneous iteration and mutation of the same collection. Split the read phase from the write phase, and the ownership model becomes clear.
这里的问题不是 Rust 故意刁难,而是同一个集合在遍历期间又被修改。把“读”和“写”拆开以后,所有权关系就清楚了。
Pitfall 2: “Too Many String Types”
坑点 2:“字符串类型怎么这么多”
#![allow(unused)]
fn main() {
// When in doubt:
// - &str for function parameters
// - String for struct fields and return values
// - &str literals ("hello") work everywhere &str is expected
fn process(input: &str) -> String { // Accept &str, return String
format!("Processed: {}", input)
}
}
When uncertain, remember this rule of thumb: use &str for input, use String for owned stored data and returned values.
拿不准时记住这条朴素经验:输入参数优先 &str,需要持有的数据和返回值再用 String。
Pitfall 3: “I Miss Python’s Simplicity”
坑点 3:“还是 Python 一行推导式看着舒服”
#![allow(unused)]
fn main() {
// Python one-liner:
// result = [x**2 for x in data if x > 0]
// Rust equivalent:
let result: Vec<i32> = data.iter()
.filter(|&&x| x > 0)
.map(|&x| x * x)
.collect();
// It's more verbose, but:
// - Type-safe at compile time
// - 10-100x faster
// - No runtime type errors possible
// - Explicit about memory allocation (.collect())
}
Rust usually makes data flow and allocation sites more explicit. The syntax is longer, but that extra surface area buys safety and performance.
Rust 往往会把数据流和分配时机写得更明确。语法是长了一些,但换来的正是类型安全和性能可预期性。
Pitfall 4: “Where’s My REPL?”
坑点 4:“交互式 REPL 跑哪去了?”
#![allow(unused)]
fn main() {
// Rust has no REPL. Instead:
// 1. Use `cargo test` as your REPL — write small tests to try things
// 2. Use Rust Playground (play.rust-lang.org) for quick experiments
// 3. Use `dbg!()` macro for quick debug output
// 4. Use `cargo watch -x test` for auto-running tests on save
#[test]
fn playground() {
// Use this as your "REPL" — run with `cargo test playground`
let result = "hello world"
.split_whitespace()
.map(|w| w.to_uppercase())
.collect::<Vec<_>>();
dbg!(&result); // Prints: [src/main.rs:5] &result = ["HELLO", "WORLD"]
}
}
Rust does not lean on a traditional REPL workflow. Small tests, the Playground, and quick debug macros are the practical substitutes.
Rust 不是那种高度依赖传统 REPL 的语言。小测试、在线 Playground,以及 dbg!() 这类调试宏,基本就是日常试验手感的替代品。
Learning Path and Resources
学习路线与资源
Week 1-2: Foundations
第 1 到 2 周:打基础
- Install Rust and configure VS Code with rust-analyzer.
安装 Rust,并在 VS Code 中配置 rust-analyzer。 - Complete chapters 1-4 of this guide, focusing on types and control flow.
完成本指南第 1 到 4 章,重点熟悉类型和控制流。 - Rewrite 5 small Python scripts in Rust.
把 5 个小型 Python 脚本改写成 Rust。 - Get comfortable with
cargo build,cargo test, andcargo clippy.
熟悉cargo build、cargo test和cargo clippy。
Week 3-4: Core Concepts
第 3 到 4 周:掌握核心概念
- Complete chapters 5-8, covering structs, enums, ownership, and modules.
完成第 5 到 8 章,掌握结构体、枚举、所有权和模块系统。 - Rewrite a Python data processing script in Rust.
选一个 Python 数据处理脚本,用 Rust 重写。 - Practice
Option<T>andResult<T, E>until they feel natural.
反复练习Option<T>和Result<T, E>,直到形成直觉。 - Read compiler errors carefully; they are teaching material, not noise.
认真阅读编译器报错,把它当成教学材料,而不是噪声。
Month 2: Intermediate
第 2 个月:进入进阶阶段
- Complete chapters 9-12 on error handling, traits, and iterators.
完成第 9 到 12 章,重点是错误处理、trait 和迭代器。 - Build a CLI tool with
clapandserde.
用clap和serde做一个命令行工具。 - Write a PyO3 extension for a performance hotspot in an existing Python project.
为现有 Python 项目的性能热点写一个 PyO3 扩展。 - Practice iterator chains until they become as natural as comprehensions.
反复写迭代器链,直到熟悉到接近 Python 推导式的程度。
Month 3: Advanced
第 3 个月:高级主题
- Complete chapters 13-16 on concurrency, unsafe code, and testing.
完成第 13 到 16 章,重点啃并发、unsafe和测试。 - Build a web service with
axumandtokio.
用axum和tokio搭一个 Web 服务。 - Contribute to an open-source Rust project.
挑一个 Rust 开源项目开始贡献。 - Read Programming Rust for deeper understanding.
阅读 Programming Rust,把理解往深处压一层。
Recommended Resources
推荐资源
- The Rust Book: https://doc.rust-lang.org/book/ — the official handbook.
The Rust Book:官方入门书,最稳的起点。 - Rust by Example: https://doc.rust-lang.org/rust-by-example/ — learn through examples.
Rust by Example:通过例子上手,适合边看边敲。 - Rustlings: https://github.com/rust-lang/rustlings — small guided exercises.
Rustlings:分解成小练习,特别适合强化基础。 - Rust Playground: https://play.rust-lang.org/ — online compiler for quick experiments.
Rust Playground:在线编译器,适合快速试验。 - This Week in Rust: https://this-week-in-rust.org/ — ecosystem newsletter.
This Week in Rust:跟踪生态动态的周刊。 - PyO3 Guide: https://pyo3.rs/ — Python and Rust interop guide.
PyO3 Guide:Python 与 Rust 互操作的主参考资料。 - Comprehensive Rust: https://google.github.io/comprehensive-rust/ — structured course material.
Comprehensive Rust:结构化课程资料,适合系统复习。
Python → Rust Rosetta Stone
Python → Rust 对照表
| Python | Rust | Chapter 章节 |
|---|---|---|
list | Vec<T> | 5 |
dict | HashMap<K,V> | 5 |
set | HashSet<T> | 5 |
tuple | (T1, T2, ...) | 5 |
class | struct + impl | 5 |
@dataclass | #[derive(...)] | 5, 12a |
Enum | enum | 6 |
None | Option<T> | 6 |
raise/try/except | Result<T,E> + ? | 9 |
Protocol (PEP 544) | trait | 10 |
TypeVar | Generic parameters <T> | 10 |
__dunder__ methods | Traits (Display, Add, etc.)Trait,例如 Display、Add | 10 |
lambda | |args| body | 12 |
generator yield | impl Iterator | 12 |
| list comprehension | .map().filter().collect() | 12 |
@decorator | Higher-order function or macro 高阶函数或宏 | 12a, 15 |
asyncio | tokio | 13 |
threading | std::thread | 13 |
multiprocessing | rayon | 13 |
unittest.mock | mockall | 14a |
pytest | cargo test + rstest | 14a |
pip install | cargo add | 8 |
requirements.txt | Cargo.lock | 8 |
pyproject.toml | Cargo.toml | 8 |
with | Scope-based Drop基于作用域的 Drop | 15 |
json.dumps/loads | serde_json | 15 |
Final Thoughts for Python Developers
给 Python 开发者的最后几句提醒
What you'll miss from Python:
- REPL and interactive exploration
- Rapid prototyping speed
- Rich ML/AI ecosystem (PyTorch, etc.)
- "Just works" dynamic typing
- pip install and immediate use
What you'll gain from Rust:
- "If it compiles, it works" confidence
- 10-100x performance improvement
- No more runtime type errors
- No more None/null crashes
- True parallelism (no GIL!)
- Single binary deployment
- Predictable memory usage
- The best compiler error messages in any language
The journey:
Week 1: "Why does the compiler hate me?"
Week 2: "Oh, it's actually protecting me from bugs"
Month 1: "I see why this matters"
Month 2: "I caught a bug at compile time that would've been a production incident"
Month 3: "I don't want to go back to untyped code"
Month 6: "Rust has made me a better programmer in every language"
从 Python 迁移时,最容易怀念的东西:- REPL 和交互式探索体验
- 原型阶段极高的速度
- 成熟的 ML、AI 生态,比如 PyTorch
- “先写再说”的动态类型体验
- `pip install` 之后马上就能用的轻快感
而 Rust 带来的收益通常更硬核:
- “只要能编过,往往就更靠谱”的信心
- 10 到 100 倍量级的性能提升空间
- 少很多运行时类型错误
- 少很多 `None` 或空值导致的崩溃
- 真正的并行能力,没有 GIL 的天花板
- 单一二进制部署更省心
- 内存占用更可控、更可预测
- 编译器错误信息质量极高
这条路的常见心理变化:
第 1 周:“这编译器怎么谁都不放过?”
第 2 周:“哦,原来它真在替我挡坑。”
第 1 个月:“开始明白这套东西的价值了。”
第 2 个月:“有些线上事故,居然在编译期就能拦住。”
第 3 个月:“再回去写完全无类型约束的代码,会有点不适应。”
第 6 个月:“Rust 反过来提升了写其他语言时的基本功。”
Exercises
练习
🏋️ Exercise: Code Review Checklist
🏋️ 练习:代码审查清单
Challenge: Review the Rust code below, written by a Python developer, and identify five improvements that would make it more idiomatic.
挑战:阅读下面这段由 Python 开发者写出的 Rust 代码,指出 5 个可以让它更符合 Rust 风格的改进点。
fn get_name(names: Vec<String>, index: i32) -> String {
if index >= 0 && (index as usize) < names.len() {
return names[index as usize].clone();
} else {
return String::from("");
}
}
fn main() {
let mut result = String::from("");
let names = vec!["Alice".to_string(), "Bob".to_string()];
result = get_name(names.clone(), 0);
println!("{}", result);
}
🔑 Solution
🔑 参考答案
Five improvements:
可以至少指出下面这 5 个改进点:
// 1. Take &[String] not Vec<String> (don't take ownership of the whole vec)
// 2. Use usize for index (not i32 — indices are always non-negative)
// 3. Return Option<&str> instead of empty string (use the type system!)
// 4. Use .get() instead of bounds-checking manually
// 5. Don't clone() in main — pass a reference
fn get_name(names: &[String], index: usize) -> Option<&str> {
names.get(index).map(|s| s.as_str())
}
fn main() {
let names = vec!["Alice".to_string(), "Bob".to_string()];
match get_name(&names, 0) {
Some(name) => println!("{name}"),
None => println!("Not found"),
}
}
Key takeaway: The most damaging Python habits in Rust are cloning everything, using sentinel values such as "", taking ownership where borrowing is enough, and using signed integers for indexing.
核心收获:在 Rust 里最伤的几种 Python 习惯,通常是到处克隆、用 "" 这种哨兵值表达失败、能借用却硬拿所有权,以及用有符号整数做索引。
End of Rust for Python Programmers Training Guide
Rust 面向 Python 程序员训练指南完
Capstone Project: CLI Task Manager §§ZH§§ 综合项目:命令行任务管理器
Capstone Project: Build a CLI Task Manager
综合项目:构建一个命令行任务管理器
What you’ll learn: Tie together everything from the course by building a complete Rust CLI application that a Python developer might otherwise write with
argparse、json、pathliband a few helper classes.
本章将学习: 通过实现一个完整的 Rust 命令行应用,把整本书的核心知识串起来。这个程序放在 Python 世界里,大概率会用argparse、json、pathlib再加几个辅助类来完成。Difficulty: 🔴 Advanced
难度: 🔴 高级
This capstone exercises concepts from nearly every major chapter in the book.
这个综合项目会把前面几乎所有重要章节都重新调动一遍。
| Chapter | Concept |
|---|---|
| Ch. 3 | Types and variables |
| Ch. 5 | Collections |
| Ch. 6 | Enums and pattern matching |
| Ch. 7 | Ownership and borrowing |
| Ch. 8 | Modules |
| Ch. 9 | Error handling |
| Ch. 10 | Traits |
| Ch. 11 | Type conversions |
| Ch. 12 | Iterators and closures |
| 章节 | 用到的概念 |
|---|---|
| 第 3 章 | 类型与变量 |
| 第 5 章 | 集合 |
| 第 6 章 | 枚举与模式匹配 |
| 第 7 章 | 所有权与借用 |
| 第 8 章 | 模块 |
| 第 9 章 | 错误处理 |
| 第 10 章 | Trait |
| 第 11 章 | 类型转换 |
| 第 12 章 | 迭代器与闭包 |
The Project: rustdo
项目目标:rustdo
A command-line task manager, similar in spirit to the todo.txt style tools Python developers often build, storing tasks in a JSON file.
这是一个命令行任务管理器,思路上很像 Python 开发者常写的 todo.txt 风格小工具,任务数据保存在 JSON 文件里。
Python Equivalent
对应的 Python 写法
#!/usr/bin/env python3
"""A simple CLI task manager — the Python version."""
import json
import sys
from pathlib import Path
from datetime import datetime
from enum import Enum
TASK_FILE = Path.home() / ".rustdo.json"
class Priority(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class Task:
def __init__(self, id: int, title: str, priority: Priority, done: bool = False):
self.id = id
self.title = title
self.priority = priority
self.done = done
self.created = datetime.now().isoformat()
def load_tasks() -> list[Task]:
if not TASK_FILE.exists():
return []
data = json.loads(TASK_FILE.read_text())
return [Task(**t) for t in data]
def save_tasks(tasks: list[Task]):
TASK_FILE.write_text(json.dumps([t.__dict__ for t in tasks], indent=2))
# Commands: add, list, done, remove, stats
# ... (you know how this goes in Python)
Your Rust Implementation
对应的 Rust 实现
Build this step by step. Each step deliberately maps back to ideas from earlier chapters.
下面按步骤来做,每一步都故意对照前面章节讲过的概念。
Step 1: Define the Data Model
步骤一:定义数据模型
#![allow(unused)]
fn main() {
// src/task.rs
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use chrono::Local;
/// Task priority — maps to Python's Priority(Enum)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Priority {
Low,
Medium,
High,
}
// Display trait (Python's __str__)
impl fmt::Display for Priority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Priority::Low => write!(f, "low"),
Priority::Medium => write!(f, "medium"),
Priority::High => write!(f, "high"),
}
}
}
// FromStr trait (parsing "high" → Priority::High)
impl FromStr for Priority {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"low" | "l" => Ok(Priority::Low),
"medium" | "med" | "m" => Ok(Priority::Medium),
"high" | "h" => Ok(Priority::High),
other => Err(format!("unknown priority: '{other}' (use low/medium/high)")),
}
}
}
/// A single task — maps to Python's Task class
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: u32,
pub title: String,
pub priority: Priority,
pub done: bool,
pub created: String,
}
impl Task {
pub fn new(id: u32, title: String, priority: Priority) -> Self {
Self {
id,
title,
priority,
done: false,
created: Local::now().format("%Y-%m-%dT%H:%M:%S").to_string(),
}
}
}
impl fmt::Display for Task {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let status = if self.done { "✅" } else { "⬜" };
let priority_icon = match self.priority {
Priority::Low => "🟢",
Priority::Medium => "🟡",
Priority::High => "🔴",
};
write!(f, "{} {} [{}] {} ({})", status, self.id, priority_icon, self.title, self.created)
}
}
}
Python comparison: Python would typically use
@dataclassplusEnum. Rust gets similar expressiveness fromstruct、enumandderivemacros, while keeping the serialization and parsing rules explicit.
和 Python 对照: Python 往往会写成@dataclass加Enum。Rust 则用struct、enum和派生宏拿到类似表达力,同时把序列化和解析规则写得更明确。
Step 2: Storage Layer
步骤二:存储层
#![allow(unused)]
fn main() {
// src/storage.rs
use std::fs;
use std::path::PathBuf;
use crate::task::Task;
/// Get the path to the task file (~/.rustdo.json)
fn task_file_path() -> PathBuf {
let home = dirs::home_dir().expect("Could not determine home directory");
home.join(".rustdo.json")
}
/// Load tasks from disk — returns empty Vec if file doesn't exist
pub fn load_tasks() -> Result<Vec<Task>, Box<dyn std::error::Error>> {
let path = task_file_path();
if !path.exists() {
return Ok(Vec::new());
}
let content = fs::read_to_string(&path)?; // ? propagates io::Error
let tasks: Vec<Task> = serde_json::from_str(&content)?; // ? propagates serde error
Ok(tasks)
}
/// Save tasks to disk
pub fn save_tasks(tasks: &[Task]) -> Result<(), Box<dyn std::error::Error>> {
let path = task_file_path();
let json = serde_json::to_string_pretty(tasks)?;
fs::write(&path, json)?;
Ok(())
}
}
Python comparison: Python would use
Path.read_text()plusjson.loads(). Rust does the same job throughfs::read_to_string()andserde_json::from_str(), but every failure case travels throughResultinstead of being silently assumed away.
和 Python 对照: Python 通常会用Path.read_text()配json.loads()。Rust 则用fs::read_to_string()和serde_json::from_str()做同样的事,只是所有失败情况都明确走在Result里。
Step 3: Command Enum
步骤三:命令枚举
#![allow(unused)]
fn main() {
// src/command.rs
use crate::task::Priority;
/// All possible commands — one enum variant per action
pub enum Command {
Add { title: String, priority: Priority },
List { show_done: bool },
Done { id: u32 },
Remove { id: u32 },
Stats,
Help,
}
impl Command {
/// Parse command-line arguments into a Command
/// (In production, you'd use `clap` — this is educational)
pub fn parse(args: &[String]) -> Result<Self, String> {
match args.first().map(|s| s.as_str()) {
Some("add") => {
let title = args.get(1)
.ok_or("usage: rustdo add <title> [priority]")?
.clone();
let priority = args.get(2)
.map(|p| p.parse::<Priority>())
.transpose()
.map_err(|e| e.to_string())?
.unwrap_or(Priority::Medium);
Ok(Command::Add { title, priority })
}
Some("list") => {
let show_done = args.get(1).map(|s| s == "--all").unwrap_or(false);
Ok(Command::List { show_done })
}
Some("done") => {
let id: u32 = args.get(1)
.ok_or("usage: rustdo done <id>")?
.parse()
.map_err(|_| "id must be a number")?;
Ok(Command::Done { id })
}
Some("remove") => {
let id: u32 = args.get(1)
.ok_or("usage: rustdo remove <id>")?
.parse()
.map_err(|_| "id must be a number")?;
Ok(Command::Remove { id })
}
Some("stats") => Ok(Command::Stats),
_ => Ok(Command::Help),
}
}
}
}
Python comparison: In Python, this logic would usually live in
argparseorclick. Here it is written by hand to make thematch-driven control flow obvious. In real projects,clapis the normal choice.
和 Python 对照: Python 里这类解析通常会交给argparse或click。这里手写出来,是为了把match这种分支风格看清楚。真做项目时,通常还是会直接上clap。
Step 4: Business Logic
步骤四:业务逻辑
#![allow(unused)]
fn main() {
// src/actions.rs
use crate::task::{Task, Priority};
use crate::storage;
pub fn add_task(title: String, priority: Priority) -> Result<(), Box<dyn std::error::Error>> {
let mut tasks = storage::load_tasks()?;
let next_id = tasks.iter().map(|t| t.id).max().unwrap_or(0) + 1;
let task = Task::new(next_id, title.clone(), priority);
println!("Added: {task}");
tasks.push(task);
storage::save_tasks(&tasks)?;
Ok(())
}
pub fn list_tasks(show_done: bool) -> Result<(), Box<dyn std::error::Error>> {
let tasks = storage::load_tasks()?;
let filtered: Vec<&Task> = tasks.iter()
.filter(|t| show_done || !t.done) // Iterator + closure (Ch. 12)
.collect();
if filtered.is_empty() {
println!("No tasks! 🎉");
return Ok(());
}
for task in &filtered {
println!(" {task}"); // Uses Display trait (Ch. 10)
}
println!("\n{} task(s) shown", filtered.len());
Ok(())
}
pub fn complete_task(id: u32) -> Result<(), Box<dyn std::error::Error>> {
let mut tasks = storage::load_tasks()?;
let task = tasks.iter_mut()
.find(|t| t.id == id) // Iterator::find (Ch. 12)
.ok_or(format!("No task with id {id}"))?;
task.done = true;
println!("Completed: {task}");
storage::save_tasks(&tasks)?;
Ok(())
}
pub fn remove_task(id: u32) -> Result<(), Box<dyn std::error::Error>> {
let mut tasks = storage::load_tasks()?;
let len_before = tasks.len();
tasks.retain(|t| t.id != id); // Vec::retain (Ch. 5)
if tasks.len() == len_before {
return Err(format!("No task with id {id}").into());
}
println!("Removed task {id}");
storage::save_tasks(&tasks)?;
Ok(())
}
pub fn show_stats() -> Result<(), Box<dyn std::error::Error>> {
let tasks = storage::load_tasks()?;
let total = tasks.len();
let done = tasks.iter().filter(|t| t.done).count();
let pending = total - done;
// Group by priority using iterators (Ch. 12)
let high = tasks.iter().filter(|t| !t.done && t.priority == Priority::High).count();
let medium = tasks.iter().filter(|t| !t.done && t.priority == Priority::Medium).count();
let low = tasks.iter().filter(|t| !t.done && t.priority == Priority::Low).count();
println!("📊 Task Statistics");
println!(" Total: {total}");
println!(" Done: {done} ✅");
println!(" Pending: {pending}");
println!(" 🔴 High: {high}");
println!(" 🟡 Medium: {medium}");
println!(" 🟢 Low: {low}");
Ok(())
}
}
Key Rust patterns used:
iter().map().max()for ID generation,filter()pluscollect()for list views,iter_mut().find()for mutation, andretain()for deletion. These are the Rust equivalents of several familiar Python collection tricks.
这里练到的 Rust 模式: 用iter().map().max()生成 ID,用filter()加collect()产出视图,用iter_mut().find()找并修改元素,用retain()删除元素。这些基本都能对上 Python 里常见的集合操作套路。
Step 5: Wire It Together
步骤五:把模块接起来
// src/main.rs
mod task;
mod storage;
mod command;
mod actions;
use command::Command;
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let command = match Command::parse(&args) {
Ok(cmd) => cmd,
Err(e) => {
eprintln!("Error: {e}");
std::process::exit(1);
}
};
let result = match command {
Command::Add { title, priority } => actions::add_task(title, priority),
Command::List { show_done } => actions::list_tasks(show_done),
Command::Done { id } => actions::complete_task(id),
Command::Remove { id } => actions::remove_task(id),
Command::Stats => actions::show_stats(),
Command::Help => {
print_help();
Ok(())
}
};
if let Err(e) = result {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
fn print_help() {
println!("rustdo — a task manager for Pythonistas learning Rust\n");
println!("USAGE:");
println!(" rustdo add <title> [low|medium|high] Add a task");
println!(" rustdo list [--all] List pending tasks");
println!(" rustdo done <id> Mark task complete");
println!(" rustdo remove <id> Remove a task");
println!(" rustdo stats Show statistics");
}
graph TD
CLI["main.rs<br/>(CLI entry)<br/>入口"] --> CMD["command.rs<br/>(parse args)<br/>解析命令"]
CMD --> ACT["actions.rs<br/>(business logic)<br/>业务逻辑"]
ACT --> STORE["storage.rs<br/>(JSON persistence)<br/>JSON 持久化"]
ACT --> TASK["task.rs<br/>(data model)<br/>数据模型"]
STORE --> TASK
style CLI fill:#d4edda
style CMD fill:#fff3cd
style ACT fill:#fff3cd
style STORE fill:#ffeeba
style TASK fill:#ffeeba
这一步其实就是把“模块划分”从嘴上说变成代码结构。入口只负责解析和分发,存储只负责落盘读盘,业务逻辑则集中在 actions 里,清清楚楚。
This is the point where “good module boundaries” become real code instead of vague advice. The entry point parses and dispatches, storage persists data, and the business rules stay concentrated in one place.
Step 6: Cargo.toml Dependencies
步骤六:Cargo.toml 依赖
[package]
name = "rustdo"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = "0.4"
dirs = "5"
Python equivalent: Think of this as the dependency section of
pyproject.toml.cargo add serde serde_json chrono dirsfills the same role as installing packages and recording them for the project.
和 Python 对照: 可以把它看成pyproject.toml里的依赖声明区域。cargo add serde serde_json chrono dirs干的事情,和安装依赖并写回项目配置差不多。
Step 7: Tests
步骤七:测试
#![allow(unused)]
fn main() {
// src/task.rs — add at the bottom
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_priority() {
assert_eq!("high".parse::<Priority>().unwrap(), Priority::High);
assert_eq!("H".parse::<Priority>().unwrap(), Priority::High);
assert_eq!("med".parse::<Priority>().unwrap(), Priority::Medium);
assert!("invalid".parse::<Priority>().is_err());
}
#[test]
fn task_display() {
let task = Task::new(1, "Write Rust".to_string(), Priority::High);
let display = format!("{task}");
assert!(display.contains("Write Rust"));
assert!(display.contains("🔴"));
assert!(display.contains("⬜")); // Not done yet
}
#[test]
fn task_serialization_roundtrip() {
let task = Task::new(1, "Test".to_string(), Priority::Low);
let json = serde_json::to_string(&task).unwrap();
let recovered: Task = serde_json::from_str(&json).unwrap();
assert_eq!(recovered.title, "Test");
assert_eq!(recovered.priority, Priority::Low);
}
}
}
Python equivalent: This is the Rust version of a small pytest suite. The difference is that the tests often live right beside the code they verify, and
cargo testalready knows how to discover them.
和 Python 对照: 这就是 Rust 版的小型 pytest 套件。区别在于测试通常就贴着被测代码放,cargo test也天然知道怎么把它们找出来。
Stretch Goals
扩展方向
Once the basic version works, these upgrades make it feel much more like a real tool.
基础版跑通以后,再加下面这些改造,味道就更像真正的工程工具了。
- Add
clapfor argument parsing
#![allow(unused)]
fn main() {
#[derive(Parser)]
enum Command {
Add { title: String, #[arg(default_value = "medium")] priority: Priority },
List { #[arg(long)] all: bool },
Done { id: u32 },
Remove { id: u32 },
Stats,
}
}
1. 接入 clap 做参数解析。这样命令行体验、帮助文本、默认值和参数校验都会更省心。
- Add colored output with the
coloredcrate.
2. 加彩色终端输出,可以用 colored crate。
- Add due dates with
Option<NaiveDate>.
3. 支持截止日期,可以把字段做成 Option<NaiveDate>。
- Add tags or categories with
Vec<String>.
4. 支持标签或分类,可以用 Vec<String> 存。
- Split into library + binary so the logic becomes reusable.
5. 拆成库加可执行程序,这样核心逻辑就能复用,不会死死绑在命令行入口里。
What You Practiced
这一章到底练到了什么
| Chapter | Concept | Where It Appeared |
|---|---|---|
| Ch. 3 | Types and variables | Task struct fields, u32, String, bool |
| Ch. 5 | Collections | Vec<Task>, retain(), push() |
| Ch. 6 | Enums + match | Priority, Command, exhaustive matching |
| Ch. 7 | Ownership + borrowing | &[Task] vs Vec<Task>, &mut for completion |
| Ch. 8 | Modules | mod task; mod storage; mod command; mod actions; |
| Ch. 9 | Error handling | Result<T, E>, ? operator, .ok_or() |
| Ch. 10 | Traits | Display, FromStr, Serialize, Deserialize |
| Ch. 11 | From/Into | FromStr for Priority, .into() for error conversion |
| Ch. 12 | Iterators | filter, map, find, count, collect |
| Ch. 14 | Testing | #[test], #[cfg(test)], assertion macros |
| 章节 | 概念 | 在哪里出现 |
|---|---|---|
| 第 3 章 | 类型与变量 | Task 字段、u32、String、bool |
| 第 5 章 | 集合 | Vec<Task>、retain()、push() |
| 第 6 章 | 枚举与 match | Priority、Command、穷尽匹配 |
| 第 7 章 | 所有权与借用 | &[Task]、Vec<Task>、修改时的 &mut |
| 第 8 章 | 模块 | mod task; mod storage; mod command; mod actions; |
| 第 9 章 | 错误处理 | Result<T, E>、?、.ok_or() |
| 第 10 章 | Trait | Display、FromStr、Serialize、Deserialize |
| 第 11 章 | 类型转换 | Priority 的 FromStr、错误的 .into() 转换 |
| 第 12 章 | 迭代器 | filter、map、find、count、collect |
| 第 14 章 | 测试 | #[test]、#[cfg(test)]、断言宏 |
Congratulations: Finishing this project means the material is no longer just chapter-by-chapter theory. At that point, the transition from “Python developer learning Rust” to “Rust developer who also knows Python” has basically already happened.
这章做完以后,前面的内容就已经不是零散知识点了。 到这一步,身份基本已经从“会 Python、正在学 Rust”切换成了“会 Rust,同时也懂 Python 的开发者”。