Rust Python 程序员指南:完整培训教程
这是一本为具有 Python 经验的开发者编写的全面 Rust 指南。本书涵盖从基础语法到高级模式的所有内容,重点关注从动态类型、垃圾回收语言转向静态类型、编译时内存安全的系统编程语言时所需的概念转变。
如何使用本书
自学路径:首先完成第一部分(第 1-6 章)—— 这些章节与你已经熟悉的 Python 概念紧密对应。第二部分(第 7-12 章)介绍 Rust 特有的概念,如所有权和 trait。第三部分(第 13-16 章)涵盖高级主题和迁移策略。
建议学习节奏:
| 章节 | 主题 | 建议时间 | 检查点 |
|---|---|---|---|
| 1–4 | 环境搭建、类型、控制流 | 1 天 | 你能用 Rust 编写一个 CLI 温度转换器 |
| 5–6 | 数据结构、枚举、模式匹配 | 1–2 天 | 你能定义带数据的枚举并进行穷尽 match 匹配 |
| 7 | 所有权和借用 | 1–2 天 | 你能解释为什么 let s2 = s1 会使 s1 失效 |
| 8–9 | 模块、错误处理 | 1 天 | 你能创建一个使用 ? 传播错误的多文件项目 |
| 10–12 | Trait、泛型、闭包、迭代器 | 1–2 天 | 你能将列表推导式转换为迭代器链 |
| 13 | 并发 | 1 天 | 你能用 Arc<Mutex<T>> 编写线程安全计数器 |
| 14 | Unsafe、PyO3、测试 | 1 天 | 你能通过 PyO3 从 Python 调用 Rust 函数 |
| 15–16 | 迁移、最佳实践 | 自行安排 | 参考资料 —— 在编写实际代码时查阅 |
| 17 | 综合项目 | 2–3 天 | 构建一个完整的 CLI 应用,综合运用所有知识 |
如何使用练习题:
- 各章节包含可折叠
<details>块中的动手练习和解答 - 务必在查看解答前先尝试完成练习。 与借用检查器的斗争是学习过程的一部分 —— 编译器的错误信息是你的老师
- 如果卡壳超过 15 分钟,展开解答,仔细研究,然后关上解答从头再试一次
- Rust Playground 让你无需本地安装就能运行代码
难度标识:
- 🟢 初学者 — 直接从 Python 概念转换
- 🟡 中级 — 需要理解所有权或 trait
- 🔴 高级 — 生命周期、异步内部、或 unsafe 代码
当你遇到困难时:
- 仔细阅读编译器错误信息 —— Rust 的错误提示非常友好
- 重读相关章节;像所有权(第 7 章)这样的概念通常需要读第二遍才能理解
- Rust 标准库文档 非常优秀 —— 搜索任何类型或方法
- 更深入的异步模式,请参阅配套的 Rust 异步编程培训
目录
第一部分 — 基础
1. 介绍与动机 🟢
- [Python 开发者选择 Rust 的理由](ch01-introduction-and-motivation.md#python-开发者选择 rust 的理由)
- [Rust 解决的 Python 常见痛点](ch01-introduction-and-motivation.md#rust-解决的 python 常见痛点)
- [何时选择 Rust 而非 Python](ch01-introduction-and-motivation.md#何时选择 rust 而非 python)
2. 入门指南 🟢
- 安装与环境搭建
- [你的第一个 Rust 程序](ch02-getting-started.md#你的第一个 rust 程序)
- Cargo vs pip/Poetry
3. 内置类型与变量 🟢
4. 控制流 🟢
5. 数据结构与集合 🟢
- 元组、数组、切片
- [结构体 vs 类](ch05-data-structures-and-collections.md#结构体 vs 类)
- Vec vs list, HashMap vs dict
6. 枚举与模式匹配 🟡
- [代数数据类型 vs 联合类型](ch06-enums-and-pattern-matching.md#代数数据类型 vs 联合类型)
- 穷尽模式匹配
- [Option 实现 None 安全](ch06-enums-and-pattern-matching.md#option-实现 none 安全)
第二部分 — 核心概念
7. 所有权与借用 🟡
8. Crate 与模块 🟢
- [Rust 模块 vs Python 包](ch08-crates-and-modules.md#rust 模块 vs-python 包)
- [Crate vs PyPI 包](ch08-crates-and-modules.md#crate-vs-pypi 包)
9. 错误处理 🟡
- [异常 vs Result](ch09-error-handling.md#异常 vs-result)
- ? 操作符
- [使用 thiserror 定义自定义错误类型](ch09-error-handling.md#使用 thiserror 定义自定义错误类型)
10. Trait 与泛型 🟡
- [Trait vs 鸭子类型](ch10-traits-and-generics.md#特征 vs 鸭子类型)
- [协议 (PEP 544) vs Trait](ch10-traits-and-generics.md#协议 pep-544-vs-特征)
- 泛型约束
11. From 和 Into Trait 🟡
- [Rust 中的类型转换](ch11-from-and-into-traits.md#rust 中的类型转换)
- From、Into、TryFrom
- 字符串转换模式
12. 闭包与迭代器 🟡
- 闭包 vs Lambda 表达式
- [迭代器 vs 生成器](ch12-closures-and-iterators.md#迭代器 vs 生成器)
- [宏:编写代码的代码](ch12-closures-and-iterators.md#为什么-rust 需要宏)
第三部分 — 高级主题与迁移
13. 并发编程 🔴
- [无 GIL:真正的并行](ch13-concurrency.md#无 gil-真正的并行)
- 线程安全:类型系统保证
- async/await 对比
14. Unsafe Rust、FFI 与测试 🔴
- [何时以及为何使用 Unsafe](ch14-unsafe-rust-and-ffi.md#何时以及为何使用 unsafe)
- [PyO3:为 Python 编写 Rust 扩展](ch14-unsafe-rust-and-ffi.md#pyo3 为 python 编写 rust 扩展)
- 单元测试 vs pytest
15. 迁移模式 🟡
- [Rust 中常见的 Python 模式](ch15-migration-patterns.md#rust-中常见的 python 模式)
- [Python 开发者必备的 Crate](ch08-crates-and-modules.md#python-开发者必备的 crate)
- 渐进式采用策略
16. 最佳实践 🟡
- [Python 开发者的地道 Rust](ch16-best-practices.md#python-开发者的地道 rust)
- 常见陷阱与解决方案
- Python→Rust 对照表
- 学习路径与资源
第四部分 — 实战项目
17. 综合项目:CLI 任务管理器 🔴
- [项目:
rustdo](ch17-capstone-project.md#项目 rustdo) - [数据模型、存储、命令、业务逻辑](ch17-capstone-project.md#步骤 1-定义数据模型 - 第 -3-6-10-11-章)
- [测试与扩展目标](ch17-capstone-project.md#步骤 7-测试 - 第 -14-章)
介绍与动机
讲师介绍与课程方法
- 讲师背景
- 微软 SCHIE(硅与云硬件基础设施工程)团队首席固件架构师
- 行业资深专家,专长于安全、系统编程(固件、操作系统、管理程序)、CPU 与平台架构、C++ 系统
- 2017 年开始在 AWS EC2 使用 Rust 编程,从此爱上这门语言
- 课程特点:尽可能采用互动式学习
- 前提假设:你已掌握 Python 及其生态系统
- 示例刻意将 Python 概念映射到 Rust 对应概念
- 欢迎随时提问
为什么 Python 开发者需要学习 Rust
你将学到: Python 开发者为何转向 Rust、真实世界的性能提升案例(Dropbox、Discord、Pydantic)、 何时应该选择 Rust 而非 Python,以及两种语言的核心理念差异。
难度: 🟢 初学者
性能:从几分钟到几毫秒
Python 在 CPU 密集型任务上以缓慢著称。而 Rust 提供了 C 语言级别的性能,同时保持高级语言的易用性。
# Python — 1000 万次调用约需 2 秒
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:.2f}秒") # 典型硬件上约 2 秒
// Rust — 同样的 1000 万次调用仅需约 0.07 秒
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!("耗时:{:.2?}", start.elapsed()); // 约 0.07 秒
}
注意: Rust 应使用 release 模式运行(
cargo run --release)以获得公平的性能对比。为何差异如此之大? Python 对每个
+操作都要通过字典查找来分发, 从堆对象中拆箱整数,并在每次操作时检查类型。而 Rust 将fibonacci直接编译为 几条 x86 的add/mov指令——与 C 编译器生成的代码几乎相同。
无需垃圾回收器的内存安全
Python 的引用计数垃圾回收机制存在已知问题:循环引用、__del__ 时机不可预测、内存碎片化。
Rust 在编译时就从根源上消除了这些问题。
# Python — 循环引用导致 CPython 的引用计数无法释放
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 # 循环引用!
# 这两个节点相互引用 — 引用计数永远不会归零
# CPython 的循环检测器*最终*会清理它们,
# 但你无法控制时机,而且会带来 GC 暂停的开销
root = Node("root")
child = Node("child")
root.add_child(child)
// Rust — 所有权机制从设计上就防止了循环引用
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);
// 当 root 被丢弃时,所有子节点也会同时被丢弃
// 确定性、零开销、无垃圾回收
}
核心洞察:在 Rust 中,子节点不持有指向父节点的引用。 如果你确实需要交叉引用(例如图结构),必须使用显式机制如
Rc<RefCell<T>>或索引—— 这使得复杂性变得显而易见,且是有意为之的设计。
Rust 解决的 Python 常见痛点
1. 运行时类型错误
Python 在生产环境中最常见的 bug:向函数传递了错误的类型。 类型提示有所帮助,但它们并非强制执行的。
# Python — 类型提示只是建议,不是规则
def process_user(user_id: int, name: str) -> dict:
return {"id": user_id, "name": name.upper()}
# 这些调用在运行时"可以运行" — 但会失败
process_user("not-a-number", 42) # TypeError: int 没有 .upper() 方法
process_user(None, "Alice") # 静默地将 None 存储为 id — bug 潜伏直到下游代码期望 int
# 即使使用 mypy,你仍然可以绕过类型检查:
data = json.loads('{"id": "oops"}') # 总是返回 Any
process_user(data["id"], data["name"]) # mypy 无法捕获这个错误
#![allow(unused)]
fn main() {
// Rust — 编译器在程序运行前就抓住所有这些错误
fn process_user(user_id: i64, name: &str) -> User {
User {
id: user_id,
name: name.to_uppercase(),
}
}
// process_user("not-a-number", 42); // ❌ 编译错误:期望 i64,找到 &str
// process_user(None, "Alice"); // ❌ 编译错误:期望 i64,找到 Option
// 参数多余会导致编译错误
// 反序列化 JSON 也是类型安全的:
#[derive(Deserialize)]
struct UserInput {
id: i64, // JSON 中必须是数字
name: String, // JSON 中必须是字符串
}
let input: UserInput = serde_json::from_str(json_str)?; // 类型不匹配时返回 Err
process_user(input.id, &input.name); // ✅ 保证类型正确
}
2. None:十亿美元的错误(Python 版本)
None 可以出现在任何期望值的地方。Python 在编译时无法防止
AttributeError: 'NoneType' object has no attribute ... 这类错误的发生。
# Python — 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) # 返回 None
print(user["name"]) # 💥 TypeError: 'NoneType' 对象不可订阅
# 即使使用 Optional 类型提示,也没有强制检查:
from typing import Optional
def get_name(user_id: int) -> Optional[str]:
return None
name: Optional[str] = get_name(1)
print(name.upper()) # 💥 AttributeError — mypy 会警告,但运行时不在乎
#![allow(unused)]
fn main() {
// Rust — None 不可能出现,除非显式处理
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()
}
let user = find_user(999); // 返回 Option<User> 的 None 变体
// println!("{}", user.name); // ❌ 编译错误:Option<User> 没有 name 字段
// 你*必须*处理 None 情况:
match find_user(999) {
Some(user) => println!("{}", user.name),
None => println!("未找到用户"),
}
// 或使用组合子:
let name = find_user(999)
.map(|u| u.name)
.unwrap_or_else(|| "Unknown".to_string());
}
3. GIL:Python 的并发天花板
Python 的全局解释器锁(GIL)意味着线程无法并行执行 Python 代码。
threading 仅适用于 I/O 密集型任务;CPU 密集型任务需要使用 multiprocessing
(带有序列化开销)或 C 扩展来实现。
# Python — 由于 GIL,线程无法加速 CPU 工作
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 个线程:{elapsed:.2f}秒") # 与 1 个线程*几乎相同*!GIL 阻止了并行
# multiprocessing"可以工作",但需要在进程间序列化数据:
from multiprocessing import Pool
with Pool(4) as p:
results = p.map(cpu_work, [10_000_000] * 4) # 快约 4 倍,但有 pickle 开销
// Rust — 真正的并行,无 GIL,无序列化开销
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 个线程:{:.2?}", start.elapsed()); // 比单线程快约 4 倍
}
使用 Rayon(Rust 的并行迭代器库),并行化更简单:
#![allow(unused)] fn main() { use rayon::prelude::*; let results: Vec<u64> = inputs.par_iter().map(|&n| cpu_work(n)).collect(); }
4. 部署和分发的痛苦
Python 的部署出了名地困难:虚拟环境配置、系统 Python 冲突、pip install 失败、
C 扩展 wheel 问题、以及包含完整 Python 运行时的 Docker 镜像过于庞大。
# Python 部署检查清单:
# 1. 哪个 Python 版本?3.9? 3.10? 3.11? 3.12?
# 2. 虚拟环境:venv、conda、poetry、pipenv?
# 3. C 扩展:需要编译器?manylinux wheel?
# 4. 系统依赖:libssl、libffi 等?
# 5. Docker:完整的 python:3.12 镜像约 1.0 GB
# 6. 启动时间:import 多的应用需要 200-500ms
# Docker 镜像:约 1 GB
# FROM python:3.12-slim
# COPY requirements.txt .
# RUN pip install -r requirements.txt
# COPY . .
# CMD ["python", "app.py"]
#![allow(unused)]
fn main() {
// Rust 部署:单个静态二进制文件,无需运行时
// cargo build --release → 一个二进制文件,约 5-20 MB
// 可复制到任何地方 — 无需 Python、无需虚拟环境、无依赖
// Docker 镜像:约 5 MB(from scratch 或 distroless)
// FROM scratch
// COPY target/release/my_app /my_app
// CMD ["/my_app"]
// 启动时间:<1ms
// 交叉编译:cargo build --target x86_64-unknown-linux-musl
}
何时选择 Rust 而非 Python
选择 Rust 的场景:
- 性能至关重要:数据管道、实时处理、计算密集型服务
- 正确性重要:金融系统、安全关键代码、协议实现
- 部署简单:单个二进制文件,无运行时依赖
- 底层控制:硬件交互、操作系统集成、嵌入式系统
- 真正的并发:CPU 密集型并行,无需 GIL 变通方案
- 内存效率:降低内存密集型服务的云成本
- 长时间运行的服务:可预测的延迟很重要(无 GC 暂停)
继续使用 Python 的场景:
- 快速原型:探索性数据分析、脚本、一次性工具
- ML/AI 工作流:PyTorch、TensorFlow、scikit-learn 生态系统
- 胶水代码:连接 API、数据转换脚本
- 团队专长:当 Rust 学习曲线不值得时
- 上市时间:开发速度胜过执行速度
- 交互式工作:Jupyter notebook、REPL 驱动开发
- 脚本编写:自动化任务、系统管理任务、快速工具
考虑两者结合(使用 PyO3 的混合方法):
- 计算密集型代码用 Rust:通过 PyO3/maturin 从 Python 调用
- 业务逻辑和编排用 Python:熟悉、高效
- 渐进式迁移:识别热点,用 Rust 扩展替换
- 兼得两者优势:Python 的生态系统 + Rust 的性能
真实世界影响:公司为何选择 Rust
Dropbox:存储基础设施
- 之前(Python):同步引擎中 CPU 使用率高、内存开销大
- 之后(Rust):性能提升 10 倍,内存减少 50%
- 结果:节省数百万美元的基础设施成本
Discord:语音/视频后端
- 之前(Python → Go):GC 暂停导致音频丢失
- 之后(Rust):持续的低延迟性能
- 结果:更好的用户体验,降低服务器成本
Cloudflare:Edge Workers
- 为何选择 Rust:WebAssembly 编译、边缘性能可预测
- 结果:Workers 以微秒级冷启动运行
Pydantic V2
- 之前:纯 Python 验证 — 大负载下缓慢
- 之后:Rust 核心(通过 PyO3)— 验证速度快 5-50 倍
- 结果:相同的 Python API,显著更快的执行速度
为何这对 Python 开发者重要:
- 互补技能:Rust 和 Python 解决不同的问题
- PyO3 桥梁:编写可从 Python 调用的 Rust 扩展
- 性能理解:了解 Python 为何慢以及如何修复热点
- 职业发展:系统编程专业知识越来越有价值
- 云成本:10 倍更快的代码 = 显著更低的基础设施支出
语言哲学对比
Python 哲学
- 可读性很重要:简洁的语法,“一种显而易见的方法”
- 自带电池:丰富的標準庫、快速原型
- 鸭子类型:“如果它像鸭子一样走路,像鸭子一样叫……”
- 开发者速度:优化编写速度,而非执行速度
- 动态一切:运行时修改类、猴子补丁、元类
Rust 哲学
- 无牺牲的性能:零成本抽象,无运行时开销
- 正确性优先:如果能编译,某些类别的 bug 就不可能发生
- 显式优于隐式:无隐藏行为,无隐式转换
- 所有权:资源恰好有一个所有者 — 内存、文件、套接字
- 无畏并发:类型系统在编译时防止数据竞争
graph LR
subgraph PY["🐍 Python"]
direction TB
PY_CODE["你的代码"] --> PY_INTERP["解释器 — CPython VM"]
PY_INTERP --> PY_GC["垃圾回收 — 引用计数 + GC"]
PY_GC --> PY_GIL["GIL — 无真正并行"]
PY_GIL --> PY_OS["操作系统/硬件"]
end
subgraph RS["🦀 Rust"]
direction TB
RS_CODE["你的代码"] --> RS_NONE["无运行时开销"]
RS_NONE --> RS_OWN["所有权 — 编译时,零成本"]
RS_OWN --> RS_THR["原生线程 — 真正并行"]
RS_THR --> RS_OS["操作系统/硬件"]
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
快速参考:Rust vs Python
| 概念 | Python | Rust | 关键差异 |
|---|---|---|---|
| 类型系统 | 动态(鸭子类型) | 静态(编译时) | 错误在编译前捕获 |
| 内存 | 垃圾回收(引用计数 + 循环 GC) | 所有权系统 | 零成本、确定性清理 |
| None/null | 任何地方的 None | Option<T> | 编译时 None 安全 |
| 错误处理 | raise/try/except | Result<T, E> | 显式、无隐藏控制流 |
| 可变性 | 全部可变 | 默认不可变 | 需要显式选择可变 |
| 速度 | 解释型(慢 10-100 倍) | 编译型(C/C++ 速度) | 快几个数量级 |
| 并发 | GIL 限制线程 | 无 GIL,Send/Sync trait | 默认真正并行 |
| 依赖管理 | pip install / poetry add | cargo add | 内置依赖管理 |
| 构建系统 | setuptools/poetry/hatch | Cargo | 单一统一工具 |
| 打包配置 | pyproject.toml | Cargo.toml | 类似的声明式配置 |
| REPL | python 交互式 | 无 REPL(用测试/cargo run) | 编译优先工作流 |
| 类型提示 | 可选、不强制 | 必需、编译器强制 | 类型不是装饰品 |
练习
🏋️ 练习:思维模型检查(点击展开)
挑战:对于每个 Python 代码片段,预测 Rust 会有什么不同要求。无需编写代码 — 只需描述约束。
x = [1, 2, 3]; y = x; x.append(4)— Rust 中会发生什么?data = None; print(data.upper())— Rust 如何防止这个错误?import threading; shared = []; threading.Thread(target=shared.append, args=(1,)).start()— Rust 要求什么?
🔑 解答
- 所有权移动:
let y = x;会移动x—x.push(4)是编译错误。你需要let y = x.clone();或者用let y = &x;借用。 - 无 null:
data不能是None,除非它是Option<String>。你必须match或使用.unwrap()/if let— 不会有意外的NoneType错误。 - Send + Sync:编译器要求
shared包裹在Arc<Mutex<Vec<i32>>>中。忘记加锁 = 编译错误,而不是竞态条件。
核心要点:Rust 将运行时失败转变为编译时错误。你感受到的“阻力“是编译器在捕获真正的 bug。
入门指南
安装与环境搭建
你将学到: 如何安装 Rust 及其工具链、Cargo 构建系统与 pip/Poetry 的对比、 IDE 设置、你的第一个
Hello, world!程序,以及 Rust 核心关键字与 Python 的对应关系。难度: 🟢 初学者
安装 Rust
# 通过 rustup 安装 Rust(Linux/macOS/WSL)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 验证安装
rustc --version # Rust 编译器
cargo --version # 构建工具 + 包管理器(类似 pip + setuptools 的组合)
# 更新 Rust
rustup update
Rust 工具 vs Python 工具
| 用途 | Python | Rust |
|---|---|---|
| 语言运行时 | python(解释器) | rustc(编译器,很少直接调用) |
| 包管理器 | pip / poetry / uv | cargo(内置) |
| 项目配置 | pyproject.toml | Cargo.toml |
| 锁定文件 | poetry.lock / requirements.txt | Cargo.lock |
| 虚拟环境 | venv / conda | 不需要(依赖按项目管理) |
| 格式化器 | black / ruff format | rustfmt(内置:cargo fmt) |
| Linter | ruff / flake8 / pylint | clippy(内置:cargo clippy) |
| 类型检查 | mypy / pyright | 编译器内置(始终开启) |
| 测试运行器 | pytest | cargo test(内置) |
| 文档工具 | sphinx / mkdocs | cargo doc(内置) |
| REPL | python / ipython | 无(用 cargo test 或 Rust Playground) |
IDE 设置
VS Code(推荐):
需要安装的扩展:
- rust-analyzer ← 核心:IDE 功能、类型提示、自动补全
- Even Better TOML ← Cargo.toml 语法高亮
- CodeLLDB ← 调试器支持
# Python 等价映射:
# rust-analyzer ≈ Pylance(但拥有 100% 类型覆盖,始终开启)
# cargo clippy ≈ ruff(但检查正确性,不仅是风格)
你的第一个 Rust 程序
Python Hello World
# hello.py — 直接运行
print("Hello, World!")
# 运行:
# python hello.py
Rust Hello World
// src/main.rs — 必须先编译
fn main() {
println!("Hello, World!"); // println! 是宏(注意 !)
}
// 构建并运行:
// cargo run
Python 开发者的关键差异
Python: Rust:
───────── ─────
- 不需要 main() - fn main() 是入口点
- 缩进 = 代码块 - 花括号 {} = 代码块
- print() 是函数 - println!() 是宏(! 很重要)
- 没有分号 - 分号结束语句
- 没有类型声明 - 类型推断但始终已知
- 解释型(直接运行) - 编译型(cargo build,然后运行)
- 运行时错误 - 大多数错误在编译时捕获
创建你的第一个项目
# Python # Rust
mkdir myproject cargo new myproject
cd myproject cd myproject
python -m venv .venv # 无需虚拟环境
source .venv/bin/activate # 无需激活
# 手动创建文件 # src/main.rs 已创建
# Python 项目结构: Rust 项目结构:
# myproject/ myproject/
# ├── pyproject.toml ├── Cargo.toml (类似 pyproject.toml)
# ├── src/ ├── src/
# │ └── myproject/ │ └── main.rs (入口点)
# │ ├── __init__.py └── (无需 __init__.py)
# │ └── main.py
# └── tests/
# └── test_main.py
graph TD
subgraph Python ["Python 项目"]
PP["pyproject.toml"] --- PS["src/"]
PS --- PM["myproject/"]
PM --- PI["__init__.py"]
PM --- PMN["main.py"]
PP --- PT["tests/"]
end
subgraph Rust ["Rust 项目"]
RC["Cargo.toml"] --- RS["src/"]
RS --- RM["main.rs"]
RC --- RTG["target/ (自动生成)"]
end
style Python fill:#ffeeba
style Rust fill:#d4edda
关键差异:Rust 项目更简单 — 没有
__init__.py、没有虚拟环境、没有setup.py、setup.cfg、pyproject.toml的困惑。只有Cargo.toml+src/。
Cargo vs pip/Poetry
项目配置
# 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" # Rust 版本(类似 Python 版本)
[dependencies]
reqwest = "0.12" # HTTP 客户端(类似 requests)
serde = { version = "1.0", features = ["derive"] } # 序列化(类似 pydantic)
[dev-dependencies]
# 测试依赖 — 仅在 `cargo test` 时编译
# (无需单独的测试配置 — `cargo test` 内置)
常用 Cargo 命令
# Python 等价 # 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 . # 编译器内置 — 始终运行
ruff check . cargo clippy
ruff format . cargo fmt
python main.py cargo run
python -c "..." # 无等价 — 用 cargo run 或测试
# Rust 特有:
cargo new myproject # 创建新项目
cargo build --release # 优化构建(比 debug 快 10-100 倍)
cargo doc --open # 生成并浏览 API 文档
cargo update # 更新依赖(类似 pip install --upgrade)
Python 开发者必知的 Rust 关键字
变量与可变性关键字
#![allow(unused)]
fn main() {
// let — 声明变量(类似 Python 赋值,但默认不可变)
let name = "Alice"; // Python: name = "Alice"(但可变)
// name = "Bob"; // ❌ 编译错误!默认不可变
// mut — 可变性修饰
let mut count = 0; // Python: count = 0(Python 中始终可变)
count += 1; // ✅ 因为有 `mut`
// const — 编译时常量(类似 Python 的 UPPER_CASE 约定,但强制)
const MAX_SIZE: usize = 1024; // Python: MAX_SIZE = 1024(只是约定)
// static — 全局变量(谨慎使用;Python 有模块级全局变量)
static VERSION: &str = "1.0";
}
所有权与借用关键字
#![allow(unused)]
fn main() {
// 这些*没有* Python 等价物 — 它们是 Rust 特有的概念
// & — 借用(只读引用)
fn print_name(name: &str) { } // Python: def print_name(name: str) — 但 Python 始终传引用
// &mut — 可变借用
fn append(list: &mut Vec<i32>) { } // Python: def append(lst: list) — Python 中始终可变
// move — 转移所有权(在 Rust 中隐式发生,Python 中从不)
let s1 = String::from("hello");
let s2 = s1; // s1 被*移动*到 s2 — s1 不再有效
// println!("{}", s1); // ❌ 编译错误:值已移动
}
类型定义关键字
#![allow(unused)]
fn main() {
// struct — 类似 Python 的 dataclass 或 NamedTuple
struct Point { // @dataclass
x: f64, // class Point:
y: f64, // x: float
} // y: float
// enum — 类似 Python 的 enum 但*强大得多*(可以携带数据)
enum Shape { // 无直接 Python 等价
Circle(f64), // 每个变体可以持有不同的数据
Rectangle(f64, f64),
}
// impl — 为类型实现方法(类似在类中定义方法)
impl Point { // class Point:
fn distance(&self) -> f64 { // def distance(self) -> float:
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
// trait — 类似 Python 的 ABC 或 Protocol(PEP 544)
trait Drawable { // class Drawable(Protocol):
fn draw(&self); // def draw(self) -> None: ...
}
// type — 类型别名(类似 Python 的 TypeAlias)
type UserId = i64; // UserId = int(或 TypeAlias)
}
控制流关键字
#![allow(unused)]
fn main() {
// match — 穷尽模式匹配(类似 Python 3.10+ 的 match,但强制)
match value {
1 => println!("one"),
2 | 3 => println!("two or three"),
_ => println!("other"), // _ = 通配符(类似 Python 的 case _:)
}
// if let — 解构 + 条件(Python 风格:if (m := regex.match(s)):)
if let Some(x) = optional_value {
println!("{}", x);
}
// loop — 无限循环(类似 while True:)
loop {
break; // 必须 break 退出
}
// for — 迭代(类似 Python 的 for,但更常用 .iter())
for item in collection.iter() { // for item in collection:
println!("{item}");
}
// while let — 带解构的循环
while let Some(item) = stack.pop() {
process(item);
}
}
可见性关键字
#![allow(unused)]
fn main() {
// pub — 公开(Python 没有真正的私有;用 _ 约定)
pub fn greet() { } // def greet(): — Python 中一切"公开"
// pub(crate) — 仅在 crate 内可见
pub(crate) fn internal() { } // def _internal(): — 单下划线约定
// (无关键字) — 模块内私有
fn private_helper() { } // def __private(): — 双下划线名称修饰
// 在 Python 中,"私有"只是约定
// 在 Rust 中,私有由编译器强制
}
练习
🏋️ 练习:第一个 Rust 程序(点击展开)
挑战:创建一个新的 Rust 项目并编写一个程序:
- 声明一个变量
name包含你的名字(类型&str) - 声明一个可变变量
count从 0 开始 - 使用
for循环从1..=5递增count并打印"Hello, {name}! (count: {count})" - 循环结束后,用
match表达式打印 count 是奇数还是偶数
🔑 解答
cargo new hello_rust && cd hello_rust
// src/main.rs
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}");
}
核心要点:
let默认不可变(你需要mut来改变count)1..=5是包含范围(对应 Python 的range(1, 6))match是返回值的表达式- 没有
self、没有if __name__ == "__main__"— 只有fn main()
内置类型与变量
变量与可变性
你将学到: 默认不可变的变量、显式的
mut、原始数值类型与 Python 任意精度int的对比、String与&str(前期最难的概念)、字符串格式化,以及 Rust 中必需的类型注解。难度: 🟢 初学者
Python 变量声明
# Python — 一切皆可变,动态类型
count = 0 # 可变,类型推断为 int
count = 5 # ✅ 可以
count = "hello" # ✅ 可以 — 类型可以改变!(动态类型)
# "常量" 只是约定:
MAX_SIZE = 1024 # 没有什么能阻止后续 MAX_SIZE = 999
Rust 变量声明
#![allow(unused)]
fn main() {
// Rust — 默认不可变,静态类型
let count = 0; // 不可变,类型推断为 i32
// count = 5; // ❌ 编译错误:不能对不可变变量二次赋值
// count = "hello"; // ❌ 编译错误:期望整数,找到 &str
let mut count = 0; // 显式可变
count = 5; // ✅ 可以
// count = "hello"; // ❌ 仍然不能改变类型
const MAX_SIZE: usize = 1024; // 真正的常量 — 由编译器强制
}
Python 开发者的关键思维转变
#![allow(unused)]
fn main() {
// Python:变量是指向对象的标签
// Rust:变量是命名的存储位置,并且"拥有"它们的值
// 变量遮蔽(shadowing)— Rust 特有,非常有用
let input = "42"; // &str
let input = input.parse::<i32>().unwrap(); // 现在是 i32 — 新变量,同名
let input = input * 2; // 现在是 84 — 又一个新变量
// Python 中,你只是重新赋值并改变类型:
input = "42"
input = int(input) # 同名,不同类型 — Python 也允许
但在 Rust 中,每个 `let` 都创建一个真正新的绑定。旧的绑定消失了。
}
实用示例:计数器
# Python 版本
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 版本
struct Counter {
value: i64,
}
impl Counter {
fn new() -> Self {
Counter { value: 0 }
}
fn increment(&mut self) { // &mut self = 我将修改这个对象
self.value += 1;
}
fn get_value(&self) -> i64 { // &self = 我只读取这个对象
self.value
}
}
fn main() {
let mut c = Counter::new(); // 必须是 `mut` 才能调用 increment()
c.increment();
println!("{}", c.get_value()); // 1
}
关键差异:在 Rust 中,方法签名里的
&mut self告诉你(和编译器)increment会修改计数器。 在 Python 中,任何方法都可以修改任何东西 — 你必须阅读代码才能知道是否会修改。
原始类型对比
flowchart LR
subgraph Python ["Python 类型"]
PI["int<br/>(任意精度)"]
PF["float<br/>(64 位)"]
PB["bool"]
PS["str<br/>(Unicode)"]
end
subgraph Rust ["Rust 类型"]
RI["i8 / i16 / i32 / i64 / i128<br/>u8 / u16 / u32 / u64 / u128"]
RF["f32 / f64"]
RB["bool"]
RS["String / &str"]
end
PI -->|"固定大小"| RI
PF -->|"选择精度"| RF
PB -->|"相同"| RB
PS -->|"所有权与借用"| RS
style Python fill:#ffeeba
style Rust fill:#d4edda
数值类型
| Python | Rust | 说明 |
|---|---|---|
int(任意精度) | i8, i16, i32, i64, i128, isize | Rust 整数有固定大小 |
int(无符号:无独立类型) | u8, u16, u32, u64, u128, usize | 显式的无符号类型 |
float(64 位 IEEE 754) | f32, f64 | Python 只有 64 位浮点数 |
bool | bool | 相同概念 |
complex | 无内置(使用 num crate) | 系统代码中很少用 |
# Python — 一种整数类型,任意精度
x = 42 # int — 可以增长到任意大小
big = 2 ** 1000 # 仍然可以 — 数千位数字
y = 3.14 # float — 始终 64 位
#![allow(unused)]
fn main() {
// Rust — 显式大小,溢出是编译时/运行时错误
let x: i32 = 42; // 32 位有符号整数
let y: f64 = 3.14; // 64 位浮点数(等同于 Python 的 float)
let big: i128 = 2_i128.pow(100); // 128 位最大值 — 没有任意精度
// 如需任意精度:使用 `num-bigint` crate
// 下划线增强可读性(与 Python 的 1_000_000 相同):
let million = 1_000_000; // 与 Python 语法相同!
// 类型后缀语法:
let a = 42u8; // u8
let b = 3.14f32; // f32
}
大小类型(重要!)
#![allow(unused)]
fn main() {
// usize 和 isize — 指针大小的整数,用于索引
let length: usize = vec![1, 2, 3].len(); // .len() 返回 usize
let index: usize = 0; // 数组索引始终是 usize
// Python 中,len() 返回 int,索引也是 int — 没有区别。
// Rust 中,混用 i32 和 usize 需要显式转换:
let i: i32 = 5;
// let item = vec[i]; // ❌ 错误:期望 usize,找到 i32
let item = vec[i as usize]; // ✅ 显式转换
}
类型推断
#![allow(unused)]
fn main() {
// Rust 会推断类型,但类型是"固定的" — 不是动态的
let x = 42; // 编译器推断为 i32(默认整数类型)
let y = 3.14; // 编译器推断为 f64(默认浮点数类型)
let s = "hello"; // 编译器推断为 &str(字符串切片)
let v = vec![1, 2]; // 编译器推断为 Vec<i32>
// 你可以总是显式指定:
let x: i64 = 42;
let y: f32 = 3.14;
// 与 Python 不同,类型在推断后"绝不能"改变:
let x = 42;
// x = "hello"; // ❌ 错误:期望整数,找到 &str
}
字符串类型:String vs &str
这是 Python 开发者最大的意外之一。Rust 有两种主要字符串类型,而 Python 只有一种。
Python 字符串处理
# Python — 一种字符串类型,不可变,引用计数
name = "Alice" # str — 不可变,堆分配
greeting = f"Hello, {name}!" # f-string 格式化
chars = list(name) # 转换为字符列表
upper = name.upper() # 返回新字符串(不可变)
Rust 字符串类型
#![allow(unused)]
fn main() {
// Rust 有两种字符串类型:
// 1. &str(字符串切片)— 借用、不可变,像字符串数据的"视图"
let name: &str = "Alice"; // 指向二进制文件中的字符串数据
// 最接近 Python 的 str,但它是"引用"
// 2. String(拥有所有权的字符串)— 堆分配、可增长、拥有所有权
let mut greeting = String::from("Hello, "); // 拥有所有权,可以修改
greeting.push_str(name);
greeting.push('!');
// greeting 现在是 "Hello, Alice!"
}
何时使用哪个?
#![allow(unused)]
fn main() {
// 可以这样理解:
// &str = "我在看别人拥有的字符串"(只读视图)
// String = "我拥有这个字符串,可以修改它"(拥有的数据)
// 函数参数:优先使用 &str(接受两种类型)
fn greet(name: &str) -> String { // 接受 &str 和 &String
format!("Hello, {}!", name) // format! 创建新的 String
}
let s1 = "world"; // &str 字面量
let s2 = String::from("Rust"); // String
greet(s1); // ✅ &str 可以直接用
greet(&s2); // ✅ &String 自动转换为 &str(Deref 强制转换)
}
实用示例
# Python 字符串操作
name = "alice"
upper = name.upper() # "ALICE"
contains = "lic" in name # True
parts = "a,b,c".split(",") # ["a", "b", "c"]
joined = "-".join(["a", "b", "c"]) # "a-b-c"
stripped = " hello ".strip() // "hello"
replaced = name.replace("a", "A") # "Alice"
#![allow(unused)]
fn main() {
// Rust 等价写法
let name = "alice";
let upper = name.to_uppercase(); // String — 新分配
let contains = name.contains("lic"); // bool
let parts: Vec<&str> = "a,b,c".split(',').collect(); // Vec<&str>
let joined = ["a", "b", "c"].join("-"); // String
let stripped = " hello ".trim(); // &str — 无需分配!
let replaced = name.replace("a", "A"); // String
// 关键要点:有些操作返回 &str(无需分配),有些返回 String。
// .trim() 返回原始字符串的切片 — 高效!
// .to_uppercase() 必须创建新的 String — 需要分配。
}
Python 开发者:这样理解
Python str ≈ Rust &str (你通常读取字符串)
Python str ≈ Rust String (当你需要拥有/修改时)
经验法则:
- 函数参数 → 使用 &str(最灵活)
- 结构体字段 → 使用 String(结构体拥有它的数据)
- 返回值 → 使用 String(调用者需要拥有它)
- 字符串字面量 → 自动成为 &str
打印与字符串格式化
基本输出
# Python
print("Hello, World!")
print("Name:", name, "Age:", age) # 空格分隔
print(f"Name: {name}, Age: {age}") # f-string
#![allow(unused)]
fn main() {
// Rust
println!("Hello, World!");
println!("Name: {} Age: {}", name, age); // 位置占位符 {}
println!("Name: {name}, Age: {age}"); // 内联变量(Rust 1.58+,类似 f-string!)
}
格式化说明符
# Python 格式化
print(f"{3.14159:.2f}") # "3.14" — 2 位小数
print(f"{42:05d}") # "00042" — 零填充
print(f"{255:#x}") # "0xff" — 十六进制
print(f"{42:>10}") # " 42" — 右对齐
print(f"{'left':<10}|") # "left |" — 左对齐
#![allow(unused)]
fn main() {
// Rust 格式化(与 Python 非常相似!)
println!("{:.2}", 3.14159); // "3.14" — 2 位小数
println!("{:05}", 42); // "00042" — 零填充
println!("{:#x}", 255); // "0xff" — 十六进制
println!("{:>10}", 42); // " 42" — 右对齐
println!("{:<10}|", "left"); // "left |" — 左对齐
}
调试打印
# Python — repr() 和 pprint
print(repr([1, 2, 3])) # "[1, 2, 3]"
from pprint import pprint
pprint({"key": [1, 2, 3]}) # 漂亮打印
#![allow(unused)]
fn main() {
// Rust — {:?} 和 {:#?}
println!("{:?}", vec![1, 2, 3]); // "[1, 2, 3]" — Debug 格式
println!("{:#?}", vec![1, 2, 3]); // 漂亮打印的 Debug 格式
// 让你的类型可打印,派生 Debug:
#[derive(Debug)]
struct Point { x: f64, y: f64 }
let p = Point { x: 1.0, y: 2.0 };
println!("{:?}", p); // "Point { x: 1.0, y: 2.0 }"
println!("{p:?}"); // 相同,内联语法
}
快速参考
| Python | Rust | 说明 |
|---|---|---|
print(x) | println!("{}", x) 或 println!("{x}") | Display 格式 |
print(repr(x)) | println!("{:?}", x) | Debug 格式 |
f"Hello {name}" | format!("Hello {name}") | 返回 String |
print(x, end="") | print!("{x}") | 无换行(print! vs println!) |
print(x, file=sys.stderr) | eprintln!("{x}") | 打印到 stderr |
sys.stdout.write(s) | print!("{s}") | 无换行 |
类型注解:可选 vs 必需
Python 类型提示(可选,不强制)
# Python — 类型提示是文档,不是强制
def add(a: int, b: int) -> int:
return a + b
add(1, 2) # ✅
add("a", "b") # ✅ Python 不在乎 — 返回 "ab"
add(1, "2") # ✅ 直到运行时崩溃:TypeError
# Union 类型、Optional
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 类型声明(必需,编译器强制)
#![allow(unused)]
fn main() {
// Rust — 类型是强制的。永远。没有例外。
fn add(a: i32, b: i32) -> i32 {
a + b
}
add(1, 2); // ✅
// add("a", "b"); // ❌ 编译错误:期望 i32,找到 &str
// 可选值使用 Option<T>
fn find(key: &str) -> Option<i32> {
// 返回 Some(value) 或 None
Some(42)
}
// 泛型类型
fn first(items: &[i32]) -> Option<i32> {
items.first().copied()
}
// 类型别名
type UserId = i64;
type Mapping = HashMap<String, Vec<i32>>;
}
关键要点:在 Python 中,类型提示帮助你的 IDE 和 mypy,但不影响运行时。 在 Rust 中,类型就是程序 — 编译器用它们保证内存安全、防止数据竞争、消除空指针错误。
📌 另见:第 6 章 — 枚举与模式匹配 展示 Rust 类型系统如何替代 Python 的
Union类型和isinstance()检查。
练习
🏋️ 练习:温度转换器(点击展开)
挑战:编写函数 celsius_to_fahrenheit(c: f64) -> f64 和函数 classify(temp_f: f64) -> &'static str,
根据阈值返回 “cold”、“mild” 或 “hot”。打印 0、20 和 35 摄氏度的结果。使用字符串格式化。
🔑 解答
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));
}
}
核心要点:Rust 需要显式的 f64(没有隐式 int→float),for 直接迭代数组(不是 range()),
if/else 块是表达式。
控制流
条件语句
你将学到: 无需括号但需要花括号的
if/else、与 Python 迭代模型对比的loop/while/for、 表达式块(一切都返回值),以及函数签名中必需的返回类型。难度: 🟢 初学者
if/else
# Python
if temperature > 100:
print("Too hot!")
elif temperature < 0:
print("Too cold!")
else:
print("Just right")
# 三元表达式
status = "hot" if temperature > 100 else "ok"
#![allow(unused)]
fn main() {
// Rust — 需要花括号,没有冒号,`else if` 不是 `elif`
if temperature > 100 {
println!("Too hot!");
} else if temperature < 0 {
println!("Too cold!");
} else {
println!("Just right");
}
// if 是一个"表达式" — 返回值(类似 Python 三元运算符,但更强大)
let status = if temperature > 100 { "hot" } else { "ok" };
}
重要差异
#![allow(unused)]
fn main() {
// 1. 条件必须是 bool — 没有 truthy/falsy
let x = 42;
// if x { } // ❌ 错误:期望 bool,找到整数
if x != 0 { } // ✅ 需要显式比较
// Python 中,这些都是 truthy/falsy:
// if []: → False (空列表)
// if "": → False (空字符串)
// if 0: → False (零)
// if None: → False
// Rust 中,只有 bool 可以用在条件中:
let items: Vec<i32> = vec![];
// if items { } // ❌ 错误
if !items.is_empty() { } // ✅ 显式检查
let name = "";
// if name { } // ❌ 错误
if !name.is_empty() { } // ✅ 显式检查
}
循环与迭代
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"] { // 直接迭代
println!("{}", item);
}
for (i, item) in ["a", "b", "c"].iter().enumerate() { // enumerate()
println!("{}: {}", i, item);
}
// HashMap 迭代
use std::collections::HashMap;
let map = HashMap::from([("x", 1), ("y", 2)]);
for (key, value) in &map { // & 借用 map
println!("{} = {}", key, value);
}
}
Range 语法
Python: Rust: 说明:
range(5) 0..5 半开区间(不包含结束)
range(1, 10) 1..10 半开区间
range(1, 11) 1..=10 闭区间(包含结束)
range(0, 10, 2) (0..10).step_by(2) 步长(是方法,不是语法)
while 循环
# Python
count = 0
while count < 5:
print(count)
count += 1
# 无限循环
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;
}
// 无限循环 — 使用 `loop`,不是 `while true`
loop {
let data = get_input();
if data == "quit" {
break;
}
}
// loop 可以返回值!(Rust 特有)
let result = loop {
let input = get_input();
if let Ok(num) = input.parse::<i32>() {
break num; // 带值的 `break` — 就像循环的 return
}
println!("不是数字,请重试");
};
}
列表推导式 vs 迭代器链
# Python — 列表推导式
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 — 迭代器链(.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();
// 这些是"惰性"的 — 直到 .collect() 才执行
// Python 推导式是急切的(立即执行)
// Rust 迭代器对于大数据集通常更高效
}
表达式块
在 Rust 中,一切都是表达式(或可以是)。这与 Python 有很大不同,
Python 中 if/for 是语句。
# Python — if 是语句(三元除外)
if condition:
result = "yes"
else:
result = "no"
# 或三元表达式(限制为单个表达式):
result = "yes" if condition else "no"
#![allow(unused)]
fn main() {
// Rust — if 是表达式(返回值)
let result = if condition { "yes" } else { "no" };
// 块是表达式 — 最后一行(没有分号)是返回值
let value = {
let x = 5;
let y = 10;
x + y // 没有分号 — 这是块的值 (15)
};
// match 也是表达式
let description = match temperature {
t if t > 100 => "boiling",
t if t > 50 => "hot",
t if t > 20 => "warm",
_ => "cold",
};
}
下图说明了 Python 基于语句和 Rust 基于表达式的控制流之间的核心差异:
flowchart LR
subgraph Python ["Python — 语句"]
P1["if condition:"] --> P2["result = 'yes'"]
P1 --> P3["result = 'no'"]
P2 --> P4["result 稍后使用"]
P3 --> P4
end
subgraph Rust ["Rust — 表达式"]
R1["let result = if cond"] --> R2["{ 'yes' }"]
R1 --> R3["{ 'no' }"]
R2 --> R4["直接返回值"]
R3 --> R4
end
style Python fill:#ffeeba
style Rust fill:#d4edda
分号规则:在 Rust 中,块中的最后一个表达式不带分号是块的返回值。 添加分号会使其成为语句(返回
())。 这最初会让 Python 开发者感到困惑 — 它就像一个隐式的return。
函数与类型签名
Python 函数
# Python — 类型可选,动态分派
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}!"
# 默认参数,*args,**kwargs
def flexible(*args, **kwargs):
pass
# 一等函数
def apply(f, x):
return f(x)
result = apply(lambda x: x * 2, 5) # 10
Rust 函数
#![allow(unused)]
fn main() {
// Rust — 函数签名上"必需"类型,没有默认值
fn greet(name: &str, greeting: &str) -> String {
format!("{}, {}!", greeting, name)
}
// 没有默认参数 — 使用构建器模式或 Option
fn greet_with_default(name: &str, greeting: Option<&str>) -> String {
let greeting = greeting.unwrap_or("Hello");
format!("{}, {}!", greeting, name)
}
// 没有 *args/**kwargs — 使用切片或结构体
fn sum_all(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
// 一等函数和闭包
fn apply(f: fn(i32) -> i32, x: i32) -> i32 {
f(x)
}
let result = apply(|x| x * 2, 5); # 10
}
返回值
# Python — return 是显式的,None 是隐式的
def divide(a, b):
if b == 0:
return None # 或抛出异常
return a / b
#![allow(unused)]
fn main() {
// Rust — 最后一个表达式是返回值(没有分号)
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None # 提前返回(也可以写 `return None;`)
} else {
Some(a / b) # 最后一个表达式 — 隐式返回
}
}
}
多个返回值
# Python — 返回元组
def min_max(numbers):
return min(numbers), max(numbers)
lo, hi = min_max([3, 1, 4, 1, 5])
#![allow(unused)]
fn main() {
// Rust — 返回元组(相同概念!)
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]);
}
方法:self vs &self vs &mut self
#![allow(unused)]
fn main() {
// Python 中,`self` 始终是对可变对象的引用。
// Rust 中,你可以选择:
impl MyStruct {
fn new() -> Self { ... } // 无 self — "静态方法" / "类方法"
fn read_only(&self) { ... } // &self — 不可变借用(不能修改)
fn modify(&mut self) { ... } // &mut self — 可变借用(可以修改)
fn consume(self) { ... } // self — 获取所有权(对象被移动)
}
// Python 等价:
// class MyStruct:
// @classmethod
// def new(cls): ... # 不需要实例
// def read_only(self): ... # 这三个在 Python 中是一样的:
// def modify(self): ... # Python self 始终可变
// def consume(self): ... # Python 从不"消耗" self
}
练习
🏋️ 练习:使用表达式的 FizzBuzz(点击展开)
挑战:使用 Rust 基于表达式的 match 为 1..=30 编写 FizzBuzz。每个数字应该打印 “Fizz”、“Buzz”、“FizzBuzz” 或数字本身。
使用 match (n % 3, n % 5) 作为表达式。
🔑 解答
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}");
}
}
核心要点:match 是一个返回值的表达式 — 不需要 if/elif/else 链。_ 通配符替代了 Python 的 case _: 默认情况。
数据结构与集合
元组与解构
你将学到: Rust 元组与 Python 元组的对比、数组和切片、结构体(Rust 替代类的方案)、
Vec<T>与list、HashMap<K,V>与dict,以及用于领域建模的新类型模式。难度: 🟢 初学者
Python 元组
# Python — 元组是不可变序列
point = (3.0, 4.0)
x, y = point # 解包
print(f"x={x}, y={y}")
# 元组可以包含混合类型
record = ("Alice", 30, True)
name, age, active = record
# 命名元组增强可读性
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
p = Point(3.0, 4.0)
print(p.x) # 命名访问
Rust 元组
#![allow(unused)]
fn main() {
// Rust — 元组是固定大小、类型固定的,可以包含混合类型
let point: (f64, f64) = (3.0, 4.0);
let (x, y) = point; // 解构(与 Python 解包相同)
println!("x={x}, y={y}");
// 混合类型
let record: (&str, i32, bool) = ("Alice", 30, true);
let (name, age, active) = record;
// 按索引访问(与 Python 不同,使用 .0 .1 .2 语法)
let first = record.0; // "Alice"
let second = record.1; // 30
// Python: record[0]
// Rust: record.0 ← 点号索引,不是方括号索引
}
何时使用元组 vs 结构体
#![allow(unused)]
fn main() {
// 元组:快速分组、函数返回值、临时值
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]);
// 结构体:命名字段、意图清晰、方法
struct Point { x: f64, y: f64 }
// 经验法则:
// - 2-3 个相同类型字段 → 元组即可
// - 需要命名字段 → 使用结构体
// - 需要方法 → 使用结构体
// (与 Python 相同的指导:tuple vs namedtuple vs dataclass)
}
数组与切片
Python 列表 vs Rust 数组
# Python — 列表是动态的、可包含异质类型
numbers = [1, 2, 3, 4, 5] # 可以增长、缩小、包含混合类型
numbers.append(6)
mixed = [1, "two", 3.0] # 允许混合类型
#![allow(unused)]
fn main() {
// Rust 有"两种"类型:固定大小和动态大小
// 1. 数组 — 固定大小,栈上分配(没有 Python 等价物)
let numbers: [i32; 5] = [1, 2, 3, 4, 5]; // 大小是类型的一部分!
// numbers.push(6); // ❌ 数组不能增长
// 所有元素初始化为相同值:
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// 2. 切片 — 数组或 Vec 的视图(类似 Python 切片,但是借用的)
let slice: &[i32] = &numbers[1..4]; // [2, 3, 4] — 是引用,不是拷贝!
// Python: numbers[1:4] 创建新列表(拷贝)
// Rust: &numbers[1..4] 创建视图(无拷贝,无分配)
}
实用对比
# Python 切片 — 创建拷贝
data = [10, 20, 30, 40, 50]
first_three = data[:3] # 新列表:[10, 20, 30]
last_two = data[-2:] # 新列表:[40, 50]
reversed_data = data[::-1] # 新列表:[50, 40, 30, 20, 10]
#![allow(unused)]
fn main() {
// Rust 切片 — 创建视图(引用)
let data = [10, 20, 30, 40, 50];
let first_three = &data[..3]; // &[i32], 视图:[10, 20, 30]
let last_two = &data[3..]; // &[i32], 视图:[40, 50]
// 没有负索引 — 使用 .len()
let last_two = &data[data.len()-2..]; // &[i32], 视图:[40, 50]
// 反转:使用迭代器
let reversed: Vec<i32> = data.iter().rev().copied().collect();
}
结构体 vs 类
Python 类
# Python — 带 __init__、方法、属性的类
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 结构体
// Rust — 结构体 + impl 块(没有继承!)
#[derive(Debug, Clone)]
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// "构造函数" — 关联函数(无 self)
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height } // 字段名相同时的简写
}
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 的 __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 对象(堆)"]
PH["PyObject 头<br/>(refcount + type ptr)"] --> PW["width: float obj"]
PH --> PHT["height: float obj"]
PH --> PD["__dict__"]
end
subgraph Rust ["Rust 结构体(栈)"]
RW["width: f64<br/>(8 字节)"] --- RH["height: f64<br/>(8 字节)"]
end
style Python fill:#ffeeba
style Rust fill:#d4edda
内存洞察:Python
Rectangle对象有 56 字节的头部 + 独立的堆分配浮点对象。RustRectangle正好是栈上的 16 字节 — 无间接、无 GC 压力。📌 另见:第 10 章 — 特征与泛型 涵盖为你的结构体实现
Display、Debug等特征以及运算符重载。
关键映射:Python 魔术方法 → Rust 特征
| Python | Rust | 用途 |
|---|---|---|
__str__ | impl Display | 人类可读字符串 |
__repr__ | #[derive(Debug)] | 调试表示 |
__eq__ | #[derive(PartialEq)] | 相等性比较 |
__hash__ | #[derive(Hash)] | 可哈希(用于 dict 键 / HashSet) |
__lt__, __le__ 等 | #[derive(PartialOrd, Ord)] | 排序 |
__add__ | impl Add | + 运算符 |
__iter__ | impl Iterator | 迭代 |
__len__ | .len() 方法 | 长度 |
__enter__/__exit__ | RAII + impl Drop | 自动清理;没有直接对应 Python 上下文管理器两阶段协议的等价物 |
__init__ | fn new()(约定) | 构造函数 |
__getitem__ | impl Index | 使用 [] 索引 |
__contains__ | .contains() 方法 | in 运算符 |
没有继承 — 使用组合替代
# Python — 继承
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 — 特征 + 组合(没有继承!)
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)
}
}
// 使用特征对象实现多态(类似 Python 的鸭子类型):
fn animal_roll_call(animals: &[&dyn Animal]) {
for a in animals {
println!("{}", a.speak());
}
}
}
思维模型:Python 说“继承行为“。Rust 说“实现契约“。 两者效果相似,但 Rust 避免了菱形问题和脆弱基类问题。
Vec vs list
Vec<T> 是 Rust 的可增长、堆分配数组 — 最接近 Python list 的等价物。
创建向量
# 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! 宏(类似列表字面量)
let empty: Vec<i32> = Vec::new(); // 空向量(需要类型注解)
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]
}
常见操作
# Python 列表操作
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() # 原地排序
sorted_copy = sorted(nums) # 新排序列表
nums.reverse() # 原地反转
contains = 3 in nums # True
index = nums.index(3) # 第一个 3 的索引
#![allow(unused)]
fn main() {
// Rust Vec 操作
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(); # 原地排序
let mut sorted_copy = nums.clone();
sorted_copy.sort(); # 排序克隆
nums.reverse(); # 原地反转
let contains = nums.contains(&3); // true
let index = nums.iter().position(|&x| x == 3); // 返回 Some(索引) 或 None
}
快速参考
| Python | Rust | 说明 |
|---|---|---|
lst.append(x) | vec.push(x) | |
lst.extend(other) | vec.extend(other) | |
lst.pop() | vec.pop() | 返回 Option<T> |
lst.insert(i, x) | vec.insert(i, x) | |
lst.remove(x) | vec.iter().position(|v| v == &x).map(|i| vec.remove(i)) | 仅移除第一个匹配项(用 retain 移除所有) |
del lst[i] | vec.remove(i) | 返回移除的元素 |
len(lst) | vec.len() | |
x in lst | vec.contains(&x) | |
lst.sort() | vec.sort() | |
sorted(lst) | 克隆 + 排序,或迭代器 | |
lst[i] | vec[i] | 越界则 panic |
lst.get(i, default) | vec.get(i) | 返回 Option<&T> |
lst[1:3] | &vec[1..3] | 返回切片(无拷贝) |
HashMap vs dict
HashMap<K, V> 是 Rust 的哈希表 — 等同于 Python 的 dict。
创建 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();
}
常见操作
# Python dict 操作
d = {"a": 1, "b": 2}
d["c"] = 3 # 插入
val = d["a"] # 1(缺失则 KeyError)
val = d.get("z", 0) # 0(缺失时的默认值)
del d["b"] # 移除
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 操作
use std::collections::HashMap;
let mut d = HashMap::new();
d.insert("a", 1);
d.insert("b", 2);
d.insert("c", 3); # 插入或覆盖
let val = d["a"]; # 1(缺失则 panic)
let val = d.get("z").copied().unwrap_or(0); # 0(安全访问)
d.remove("b"); # 移除
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 的 setdefault / defaultdict 模式
let mut word_count: HashMap<&str, i32> = HashMap::new();
for word in words {
*word_count.entry(word).or_insert(0) += 1;
}
}
快速参考
| Python | Rust | 说明 |
|---|---|---|
d[key] = val | d.insert(key, val) | 返回 Option<V>(旧值) |
d[key] | d[&key] | 缺失则 panic |
d.get(key) | d.get(&key) | 返回 Option<&V> |
d.get(key, default) | d.get(&key).unwrap_or(&default) | |
key in d | d.contains_key(&key) | |
del d[key] | d.remove(&key) | 返回 Option<V> |
d.keys() | d.keys() | 迭代器 |
d.values() | d.values() | 迭代器 |
d.items() | d.iter() | (&K, &V) 的迭代器 |
len(d) | d.len() | |
d.update(other) | d.extend(other) | |
defaultdict(int) | .entry().or_insert(0) | Entry API |
d.setdefault(k, v) | d.entry(k).or_insert(v) | Entry API |
其他集合
| Python | Rust | 说明 |
|---|---|---|
set() | HashSet<T> | use std::collections::HashSet; |
collections.deque | VecDeque<T> | use std::collections::VecDeque; |
heapq | BinaryHeap<T> | 默认最大堆 |
collections.OrderedDict | IndexMap(crate) | HashMap 不保证顺序 |
sortedcontainers.SortedList | BTreeSet<T> / BTreeMap<K,V> | 基于树,已排序 |
练习
🏋️ 练习:词频计数器(点击展开)
挑战:编写一个函数,接受 &str 句子,返回 HashMap<String, usize> 的词频(不区分大小写)。
Python 中是 Counter(s.lower().split())。把它翻译成 Rust。
🔑 解答
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}");
}
}
核心要点:HashMap::entry().or_insert() 是 Python defaultdict 或 Counter 的 Rust 等价物。
需要 * 解引用,因为 or_insert 返回 &mut usize。
枚举与模式匹配
代数数据类型 vs Union 类型
你将学到: 带数据的 Rust 枚举与 Python
Union类型的对比、穷尽式match与match/case、Option<T>作为None的编译时替代方案,以及守卫模式。难度: 🟡 中级
Python 3.10 引入了 match 语句和类型联合。Rust 的枚举走得更远 —
每个变体可以携带不同的数据,编译器确保你处理所有情况。
Python Union 类型和 Match
# Python 3.10+ — 结构模式匹配
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] # 类型别名 — 联合类型
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
# 遗漏情况时没有编译器警告!
# 添加新形状?只能 grep 代码库,希望能找到所有 match 块。
Rust 枚举 — 携带数据的变体
#![allow(unused)]
fn main() {
// Rust — 枚举变体携带数据,编译器强制穷尽匹配
enum Shape {
Circle(f64), // Circle 携带半径
Rectangle(f64, f64), // Rectangle 携带宽、高
Triangle { base: f64, height: f64 }, // 也可以使用命名字段
}
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,
// ❌ 如果你添加 Shape::Pentagon 但忘记在这里处理,
// 编译器拒绝构建。不需要 grep。
}
}
}
关键要点:Rust 的
match是穷尽的 — 编译器会验证你是否处理了 每个变体。当给枚举添加新变体时,编译器会准确指出哪些match块需要更新。 Python 的match没有这种保证。
枚举替代多个 Python 模式
# Python — Rust 枚举替代的几种模式:
# 1. 字符串常量
STATUS_PENDING = "pending"
STATUS_ACTIVE = "active"
STATUS_CLOSED = "closed"
# 2. Python Enum(无数据)
from enum import Enum
class Status(Enum):
PENDING = "pending"
ACTIVE = "active"
CLOSED = "closed"
# 3. 标签联合(类 + 类型字段)
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 — 一个枚举搞定所有三种,甚至更多
// 1. 简单枚举(类似 Python 的 Enum)
enum Status {
Pending,
Active,
Closed,
}
// 2. 携带数据的枚举(标签联合 — 类型安全!)
enum Message {
Text(String),
Image { url: String, width: u32, height: u32 },
Quit, // 无数据
Move { x: i32, y: i32 },
}
}
flowchart TD
E["enum Message"] --> T["Text(String)<br/>🏷️ tag=0 + String 数据"]
E --> I["Image { url, width, height }<br/>🏷️ tag=1 + 3 个字段"]
E --> Q["Quit<br/>🏷️ tag=2 + 无数据"]
E --> M["Move { x, y }<br/>🏷️ tag=3 + 2 个字段"]
style E fill:#d4edda,stroke:#28a745
style T fill:#fff3cd
style I fill:#fff3cd
style Q fill:#fff3cd
style M fill:#fff3cd
内存洞察:Rust 枚举是“标签联合“ — 编译器存储一个判别标签 + 足够容纳最大变体的空间。 Python 的等价物(
Union[str, dict, None])无法做到这样紧凑的表示。📌 另见:第 9 章 — 错误处理 广泛使用枚举 —
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})"),
}
}
}
穷尽式模式匹配
Python 的 match — 非穷尽
# Python — 通配符情况是可选的,没有编译器帮助
def describe(value):
match value:
case 0:
return "zero"
case 1:
return "one"
# 如果忘记写默认情况,Python 会静默返回 None。
# 没有警告,也没有错误提示。
describe(42) # 返回 None — 一个静默的 bug
Rust 的 match — 编译器强制
#![allow(unused)]
fn main() {
// Rust — "必须"处理所有可能情况
fn describe(value: i32) -> &'static str {
match value {
0 => "zero",
1 => "one",
// ❌ 编译错误:非穷尽模式:`i32::MIN..=-1_i32`
// 和 `2_i32..=i32::MAX` 未覆盖
_ => "other", // _ = 全覆盖(对于开放类型是必需的)
}
}
// 对于枚举,"不"需要全覆盖 — 编译器知道所有变体:
enum Color { Red, Green, Blue }
fn color_hex(c: Color) -> &'static str {
match c {
Color::Red => "#ff0000",
Color::Green => "#00ff00",
Color::Blue => "#0000ff",
// 不需要 _ — 已覆盖所有变体
// 以后添加 Color::Yellow → 编译器会在此处报错
}
}
}
模式匹配特性
#![allow(unused)]
fn main() {
// 多个值(类似 Python 的 case 1 | 2 | 3:)
match value {
1 | 2 | 3 => println!("small"),
4..=9 => println!("medium"), // 范围模式
_ => println!("large"),
}
// 守卫(类似 Python 的 case x if x > 0:)
match temperature {
t if t > 100 => println!("boiling"),
t if t < 0 => println!("freezing"),
t => println!("normal: {t}°"),
}
// 嵌套解构
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 用于 None 安全
Option<T> 是 Python 开发者最重要的 Rust 枚举。它用类型安全的方式替代 None。
Python None
# Python — None 是可能出现在任何地方的值
def find_user(user_id: int) -> dict | None:
users = {1: {"name": "Alice"}}
return users.get(user_id)
user = find_user(999)
# user 是 None — 但没有强制你检查!
print(user["name"]) # 💥 运行时 TypeError
Rust Option
#![allow(unused)]
fn main() {
// Rust — Option<T> 强制你处理 None 情况
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 是 Option<User> — 你"不能"在不处理 None 的情况下直接使用它
// 方法 1:match
match find_user(999) {
Some(user) => println!("Found: {}", user.name),
None => println!("Not found"),
}
// 方法 2:if let(类似 Python 的 if (x := expr) is not None)
if let Some(user) = find_user(1) {
println!("Found: {}", user.name);
}
// 方法 3:unwrap_or
let name = find_user(999)
.map(|u| u.name)
.unwrap_or_else(|| "Unknown".to_string());
// 方法 4:? 运算符(在返回 Option 的函数中)
fn get_user_name(id: i64) -> Option<String> {
let user = find_user(id)?; // 如果未找到则提前返回 None
Some(user.name)
}
}
Option 方法 — Python 等价物
| 模式 | Python | Rust |
|---|---|---|
| 检查是否存在 | if x is not None: | if let Some(x) = opt { ... } |
| 默认值 | x or default | opt.unwrap_or(default) |
| 默认工厂 | x or compute() | opt.unwrap_or_else(|| compute()) |
| 存在时转换 | f(x) if x else None | opt.map(f) |
| 链式查找 | x and x.attr and x.attr.method() | opt.and_then(|x| x.method()) |
| None 时崩溃 | 无法预防 | opt.unwrap() (panic) 或 opt.expect("msg") |
| 获取或抛出 | x if x else raise | opt.ok_or(Error)? |
练习
🏋️ 练习:形状面积计算器(点击展开)
挑战:定义枚举 Shape,变体包括 Circle(f64)(半径)、Rectangle(f64, f64)(宽、高)、
Triangle(f64, f64)(底、高)。使用 match 实现方法 fn area(&self) -> f64。
创建每种形状并打印面积。
🔑 解答
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());
}
}
核心要点:Rust 枚举替代了 Python 的 Union[Circle, Rectangle, Triangle] + isinstance() 检查。
编译器确保你处理每个变体 — 添加新形状但不更新 area() 是编译错误。
所有权与借用
理解所有权
你将学到: Rust 为什么需要所有权系统(因为没有 GC!)、移动语义与 Python 引用计数的对比、 借用(
&和&mut)、生命周期基础,以及智能指针(Box、Rc、Arc)。难度: 🟡 中级
这是 Python 开发者最难理解的概念。在 Python 中,你从不需要思考谁“拥有“ 数据 — 垃圾回收器会处理。在 Rust 中,每个值恰好有一个所有者,编译器在编译时跟踪。
Python:无处不在的共享引用
# Python — 一切都是引用,gc 清理
a = [1, 2, 3]
b = a # b 和 a 指向同一个列表
b.append(4)
print(a) # [1, 2, 3, 4] — 没想到吧!a 也跟着变了
# 谁拥有这个列表?a 和 b 都引用它。
# 垃圾回收器在没有引用时释放它。
# 你从不需要思考这个。
Rust:单一所有权
#![allow(unused)]
fn main() {
// Rust — 每个值恰好有一个所有者
let a = vec![1, 2, 3];
let b = a; // 所有权"移动"到 b
// println!("{:?}", a); // ❌ 编译错误:移动后使用值
// a 不再存在。b 是唯一所有者。
println!("{:?}", b); // ✅ [1, 2, 3]
// 当 b 超出作用域,Vec 被释放。确定性。无 GC。
}
所有权的三条规则
#![allow(unused)]
fn main() {
1. 每个值恰好有一个所有者变量。
2. 当所有者超出作用域,值被丢弃(释放)。
3. 所有权可以转移(移动)但不能复制(除非实现 Clone)。
}
移动语义 — Python 开发者最难适应的概念
# Python — 赋值复制引用,不是数据
def process(data):
data.append(42)
# 原始列表被修改!
my_list = [1, 2, 3]
process(my_list)
print(my_list) # [1, 2, 3, 42] — 被 process 修改!
#![allow(unused)]
fn main() {
// Rust — 传递给函数会"移动"所有权(对于非 Copy 类型)
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); // [1, 2, 3, 42]
// 或更好 — 借用而不是移动:
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); // [1, 2, 3, 42] — 仍然属于我们
}
所有权可视化
Python: Rust:
a ──────┐ a ──→ [1, 2, 3]
├──→ [1, 2, 3]
b ──────┘ 之后:let b = a;
(a 和 b 共享一个对象) a(无效,已移动)
(引用计数 = 2) b ──→ [1, 2, 3]
(只有 b 拥有数据)
del a → 引用计数 = 1 drop(b) → 数据释放
del b → 引用计数 = 0 → 释放 (确定性,无 GC)
stateDiagram-v2
state "Python(引用计数)" as PY {
[*] --> a_owns: a = [1,2,3]
a_owns --> shared: b = a
shared --> b_only: del a (引用计数 2→1)
b_only --> freed: del b (引用计数 1→0)
note right of shared: a 和 b 都指向<br/>同一个对象
}
state "Rust(所有权移动)" as RS {
[*] --> a_owns2: let a = vec![1,2,3]
a_owns2 --> b_owns: let b = a (移动)
b_owns --> freed2: b 超出作用域
note right of b_owns: a 在移动后无效<br/>使用则编译错误
}
移动语义 vs 引用计数
Copy vs Move
#![allow(unused)]
fn main() {
// 简单类型(整数、浮点数、布尔值、字符)被"复制",不是移动
let x = 42;
let y = x; // x 被"复制"到 y(两者都有效)
println!("{x} {y}"); // ✅ 42 42
// 堆分配类型(String、Vec、HashMap)被"移动"
let s1 = String::from("hello");
let s2 = s1; // s1 被移动到 s2
// println!("{s1}"); // ❌ 错误:移动后使用值
// 要显式复制堆数据,使用 .clone()
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷贝
println!("{s1} {s2}"); // ✅ hello hello(两者都有效)
}
Python 开发者的思维模型
Python: Rust:
───────── ─────
int, float, bool Copy 类型(i32、f64、bool、char)
→ 共享引用到不可变对象 → 按位复制(按赋值)
(无真正拷贝) (始终是独立的值)
(注意:Python 会缓存小整数,而 Rust 的复制行为始终是可预测的)
list, dict, str Move 类型(Vec、HashMap、String)
→ 共享引用 → 所有权转移(不同的行为!)
→ gc 清理 → 所有者丢弃数据
→ clone with list(x) → clone with x.clone()
or copy.deepcopy(x)
Python 共享模型导致 bug 的情况
# 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)
# original 仍然是 [1, 2, 2, 3, 3, 3] — 但只是因为我们没有修改
# 如果 remove_duplicates 修改了输入,original 也会受影响
#![allow(unused)]
fn main() {
use std::collections::HashSet;
// Rust — 所有权防止意外别名
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); // 借用 — 不能修改
// original 保证不变 — 编译器通过 & 阻止了修改
}
借用与生命周期
借用规则 — 以借书为例
#![allow(unused)]
fn main() {
把所有权想象成一本实体书:
Python: 每个人都有复印件(共享引用 + GC)
Rust: 一个人拥有这本书。其他人可以:
- &书 = 看它(不可变借用,允许多个)
- &mut 书 = 在里面写(可变借用,独占)
- 书 = 送出去(移动)
}
借用规则
flowchart TD
R["借用规则"] --> IMM["✅ 多个 &T<br/>(共享/不可变)"]
R --> MUT["✅ 一个 &mut T<br/>(独占/可变)"]
R --> CONFLICT["❌ &T + &mut T<br/>(永不同时)"]
IMM --> SAFE["多读者,安全"]
MUT --> SAFE2["单写者,安全"]
CONFLICT --> ERR["编译错误!"]
style IMM fill:#d4edda
style MUT fill:#d4edda
style CONFLICT fill:#f8d7da
style ERR fill:#f8d7da,stroke:#dc3545
#![allow(unused)]
fn main() {
// 规则 1:你可以有多个不可变借用"或"一个可变借用(不能同时)
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); // ❌ 错误:可变借用存在时不能使用不可变借用
// 这在编译时防止数据竞争!
// Python 没有等价物 — 所以 Python 的 dict 在迭代时被修改会导致运行时崩溃。
}
生命周期 — 简介
#![allow(unused)]
fn main() {
// 生命周期回答:"这个引用活多久?"
// 通常编译器推断它们。你很少显式编写。
// 简单情况 — 编译器处理:
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
// 编译器知道:返回的 &str 与输入 &str 活得一样长
// 当你需要显式生命周期时(罕见):
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
// 'a 说:"返回值与两个输入活得一样长"
}
给 Python 开发者:初期不要担心生命周期。编译器会在需要时告诉你, 95% 的情况下它都会自动推断。把生命周期注解想象成在编译器无法自己推断关系时 给出的提示。
智能指针
对于单一所有权太受限的情况,Rust 提供智能指针。 这些更接近 Python 的所有权模型 — 但是显式的、可选的。
#![allow(unused)]
fn main() {
// Box<T> — 堆分配,单一所有者(类似 Python 的正常分配)
let boxed = Box::new(42); // 堆分配的 i32
// Rc<T> — 引用计数(类似 Python 的引用计数!)
use std::rc::Rc;
let shared = Rc::new(vec![1, 2, 3]);
let clone1 = Rc::clone(&shared); // 增加引用计数
let clone2 = Rc::clone(&shared); // 增加引用计数
// 三者指向同一个 Vec。当全部丢弃时,Vec 被释放。
// 类似 Python 的引用计数,但 Rc"不"处理循环 —
// 使用 Weak<T> 打破循环(Python 的 GC 自动处理循环)
// Arc<T> — 原子引用计数(用于多线程的 Rc)
use std::sync::Arc;
let thread_safe = Arc::new(vec![1, 2, 3]);
// 跨线程共享时使用 Arc(Rc 是单线程的)
// RefCell<T> — 运行时借用检查(类似 Python 的"什么都可以"模型)
use std::cell::RefCell;
let cell = RefCell::new(42);
*cell.borrow_mut() = 99; // 运行时可变借用(如果双重借用则 panic)
}
何时使用哪一个
| 智能指针 | Python 类比 | 用例 |
|---|---|---|
Box<T> | 正常分配 | 大数据、递归类型、特征对象 |
Rc<T> | Python 默认引用计数 | 共享所有权,单线程 |
Arc<T> | 线程安全引用计数 | 共享所有权,多线程 |
RefCell<T> | Python 的“直接修改它“ | 内部可变性(后门) |
Rc<RefCell<T>> | Python 正常对象模型 | 共享 + 可变(图结构) |
关键要点:
Rc<RefCell<T>>给你 Python 般的语义(共享、可变的数据), 但你必须显式选择它。Rust 的默认行为(拥有、移动)更快,避免了引用计数的开销。 对于有循环的图状结构,使用Weak<T>打破引用循环 — 与 Python 不同, Rust 的Rc没有循环回收机制。
📌 另见:第 13 章 — 并发 涵盖多线程共享状态的
Arc<Mutex<T>>。
练习
🏋️ 练习:找出借用检查器错误(点击展开)
挑战:以下代码有 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}!")
}
🔑 解答
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 { // 接受 &str,不是 String
format!("Hello, {name}!")
}
修复的错误:
- 不可变借用 + 可变:
first借用names,然后push修改它。修复:在 push 之前使用first。 - 从 Vec 移动:
names[0]尝试从 Vec 移动 String(不允许)。修复:用&names[0]借用。 - 函数获取所有权:
make_greeting(String)消耗值。修复:改为接受&str。
Crate 与模块
Rust 模块 vs Python 包
你将学到:
mod和use与 Pythonimport的对比、可见性(pub)与 Python 基于约定的隐私、 Cargo.toml 与 pyproject.toml、crates.io 与 PyPI,以及工作区与单仓库。难度: 🟢 初学者
Python 模块系统
# Python — 文件是模块,带 __init__.py 的目录是包
# myproject/
# ├── __init__.py # 使其成为包
# ├── main.py
# ├── utils/
# │ ├── __init__.py # 使 utils 成为子包
# │ ├── helpers.py
# │ └── validators.py
# └── models/
# │ ├── __init__.py
# │ ├── user.py
# │ └── product.py
# 导入:
from myproject.utils.helpers import format_name
from myproject.models.user import User
import myproject.utils.validators as validators
Rust 模块系统
#![allow(unused)]
fn main() {
// Rust — mod 声明创建模块树,文件提供内容
// src/
// ├── main.rs # Crate 根 — 声明模块
// ├── utils/
// │ ├── mod.rs # 模块声明(类似 __init__.py)
// │ ├── helpers.rs
// │ └── validators.rs
// └── models/
// ├── mod.rs
// ├── user.rs
// └── product.rs
// 在 src/main.rs 中:
mod utils; // 告诉 Rust 去查找 src/utils/mod.rs
mod models; // 告诉 Rust 去查找 src/models/mod.rs
use utils::helpers::format_name;
use models::user::User;
// 在 src/utils/mod.rs 中:
pub mod helpers; // 声明并重新导出 helpers.rs
pub mod validators; // 声明并重新导出 validators.rs
}
graph TD
A["main.rs<br/>(crate 根)"] --> 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 等价:把
mod.rs想象成__init__.py— 它声明模块导出什么。 crate 根(main.rs/lib.rs)就像你的顶层包的__init__.py。
关键差异
| 概念 | Python | Rust |
|---|---|---|
| 模块 = 文件 | ✅ 自动 | 必须用 mod 声明 |
| 包 = 目录 | __init__.py | mod.rs + mod 声明 |
| 默认公开 | ✅ 一切 | ❌ 默认私有 |
| 设为公开 | _前缀 约定 | pub 关键字 |
| 导入语法 | from x import y | use x::y; |
| 通配符导入 | from x import * | use x::*;(不鼓励) |
| 相对导入 | from . import sibling | use super::sibling; |
| 重新导出 | __all__ 或显式 | pub use inner::Thing; |
可见性 — 默认私有
# Python — "我们都是成年人"的约定
class User:
def __init__(self):
self.name = "Alice" # 公开(按约定)
self._age = 30 # "私有"(约定:单下划线)
self.__secret = "shhh" # 名称修饰(不是真正私有)
# 没有什么能阻止你访问 _age 或甚至 __secret
print(user._age) # 可以工作
print(user._User__secret) # 也可以(名称修饰)
#![allow(unused)]
fn main() {
// Rust — 私有性由编译器强制检查
pub struct User {
pub name: String, // 公开 — 任何人都可以访问
age: i32, // 私有 — 只有本模块可以访问
}
impl User {
pub fn new(name: &str, age: i32) -> Self {
User { name: name.to_string(), age }
}
pub fn age(&self) -> i32 { // 公开的 getter 方法
self.age
}
fn validate(&self) -> bool { // 私有方法
self.age > 0
}
}
// 在模块外:
let user = User::new("Alice", 30);
println!("{}", user.name); // ✅ 公开
// println!("{}", user.age); // ❌ 编译错误:字段私有
println!("{}", user.age()); // ✅ 公开方法(getter)
}
Crate vs PyPI 包
Python 包(PyPI)
# Python
pip install requests # 从 PyPI 安装
pip install "requests>=2.28" # 版本约束
pip freeze > requirements.txt # 锁定版本
pip install -r requirements.txt # 复现环境
Rust Crate(crates.io)
# Rust
cargo add reqwest # 从 crates.io 安装(添加到 Cargo.toml)
cargo add reqwest@0.12 # 版本约束
# Cargo.lock 是自动生成的 — 无需手动管理
cargo build # 下载并编译依赖
Cargo.toml vs pyproject.toml
# Rust — Cargo.toml
[package]
name = "my-project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] } # 带特性标志
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
log = "0.4"
[dev-dependencies]
mockall = "0.13"
Python 开发者必知 Crate
| Python 库 | Rust Crate | 用途 |
|---|---|---|
requests | reqwest | HTTP 客户端 |
json(标准库) | serde_json | JSON 解析 |
pydantic | serde | 序列化/验证 |
pathlib | std::path(标准库) | 路径处理 |
os / shutil | std::fs(标准库) | 文件操作 |
re | regex | 正则表达式 |
logging | tracing / log | 日志 |
click / argparse | clap | CLI 参数解析 |
asyncio | tokio | 异步运行时 |
datetime | chrono | 日期时间 |
pytest | 内置 + rstest | 测试 |
dataclasses | #[derive(...)] | 数据结构 |
typing.Protocol | Traits | 结构类型 |
subprocess | std::process(标准库) | 运行外部命令 |
sqlite3 | rusqlite | SQLite |
sqlalchemy | diesel / sqlx | ORM / SQL 工具包 |
fastapi | axum / actix-web | Web 框架 |
工作区 vs 单仓库
Python 单仓库(典型)
# Python 单仓库(各种方法,无标准)
myproject/
├── pyproject.toml # 根项目
├── packages/
│ ├── core/
│ │ ├── pyproject.toml # 每个包有自己的配置
│ │ └── src/core/...
│ ├── api/
│ │ ├── pyproject.toml
│ │ └── src/api/...
│ └── cli/
│ ├── pyproject.toml
│ └── src/cli/...
# 工具:poetry workspaces、pip -e .、uv workspaces — 无标准
Rust 工作区
# Rust — 根目录 Cargo.toml
[workspace]
members = [
"core",
"api",
"cli",
]
# 跨工作区共享依赖
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# Rust 工作区结构 — 标准化,Cargo 内置支持
myproject/
├── Cargo.toml # 工作区根
├── Cargo.lock # 所有 crate 共享一个锁定文件
├── core/
│ ├── Cargo.toml # [dependencies] serde.workspace = true
│ └── src/lib.rs
├── api/
│ ├── Cargo.toml
│ └── src/lib.rs
└── cli/
├── Cargo.toml
└── src/main.rs
# 工作区命令
cargo build # 构建所有
cargo test # 测试所有
cargo build -p core # 只构建 core crate
cargo test -p api # 只测试 api crate
cargo clippy --all # lint 所有
关键要点:Rust 工作区是一等公民,由 Cargo 内置支持。Python 单仓库需要第三方工具 (poetry、uv、pants),支持程度不一。在 Rust 工作区中,所有 crate 共享单个
Cargo.lock,确保整个项目的依赖版本一致。
练习
🏋️ 练习:模块可见性(点击展开)
挑战:给定以下模块结构,预测哪些行可以编译,哪些不能:
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
}
🔑 解答
- Line A: ✅ 编译 —
menu()是pub - Line B: ❌ 编译错误 —
secret_recipe()对kitchen外私有 - Line C: ✅ 编译 —
staff::cook()是pub,且cook()可以通过super::访问secret_recipe()(子模块可以访问父模块的私有项)
核心要点:在 Rust 中,子模块可以访问父模块的私有项(类似 Python 的 _private 约定,但由编译器强制)。
模块外部则不能。这与 Python 相反,Python 中 _private 只是一个约定提示。
错误处理
异常 vs Result
你将学到:
Result<T, E>与try/except的对比、?运算符用于简洁的错误传播、 使用thiserror的自定义错误类型、anyhow用于应用程序,以及为什么显式错误可以防止隐藏的 bug。难度: 🟡 中级
这是 Python 开发者最大的思维转变之一。Python 使用异常进行错误处理 — 错误可以从任何地方抛出,也可以在任何地方被捕获(或者干脆不处理)。
Rust 使用 Result<T, E> — 错误是必须显式处理的值。
Python 异常处理
# Python — 异常可以从任何地方抛出
import json
def load_config(path: str) -> dict:
try:
with open(path) as f:
data = json.load(f) # 可能抛出 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 {}
# 这个函数还能抛出什么异常?
# IOError?PermissionError?UnicodeDecodeError?
# 你无法从函数签名看出来!
Rust 基于 Result 的错误处理
#![allow(unused)]
fn main() {
// Rust — 错误是返回值,在函数签名中可见
use std::fs;
use serde_json::Value;
fn load_config(path: &str) -> Result<Value, ConfigError> {
let contents = fs::read_to_string(path) // 返回 Result
.map_err(|e| ConfigError::FileError(e.to_string()))?;
let data: Value = serde_json::from_str(&contents) // 返回 Result
.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),
}
}
关键差异
Python: Rust:
───────── ─────
- 错误是异常(抛出) - 错误是值(返回)
- 隐藏的控制流(栈展开) - 显式控制流(? 运算符)
- 无法从签名知道什么错误 - 必须在返回类型中声明错误
- 未捕获的异常在运行时崩溃 - 未处理的 Result 产生编译警告(总是处理它们)
- try/except 是可选的 - 处理 Result 是必需的
- 宽泛的 except 捕获一切 - match 臂是穷尽的
Result 的两个变体
#![allow(unused)]
fn main() {
// Result<T, E> 恰好有两个变体:
enum Result<T, E> {
Ok(T), // 成功 — 包含值(类似 Python 的返回值)
Err(E), // 失败 — 包含错误(类似 Python 的抛出异常)
}
// 使用 Result:
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Division by zero".to_string()) // 类似:raise ValueError("...")
} else {
Ok(a / b) // 类似:return a / b
}
}
// 处理 Result — 类似 try/except 但显式
match divide(10.0, 0.0) {
Ok(result) => println!("Result: {result}"),
Err(msg) => println!("Error: {msg}"),
}
}
? 运算符
? 运算符等同于让异常沿调用栈向上传播,但它是可见的和显式的。
Python — 隐式传播
# Python — 异常静默地沿调用栈向上传播
def read_username() -> str:
with open("config.txt") as f: # FileNotFoundError 传播
return f.readline().strip() # IOError 传播
def greet():
name = read_username() # 如果这里抛出,greet() 也抛出
print(f"Hello, {name}!") # 错误时跳过
# 错误传播是"不可见的" — 你必须阅读实现
# 才能知道什么异常可能逃逸。
Rust — 使用 ? 显式传播
#![allow(unused)]
fn main() {
// Rust — ? 传播错误,但在代码和签名中都可见
use std::fs;
use std::io;
fn read_username() -> Result<String, io::Error> {
let contents = fs::read_to_string("config.txt")?; // ? = Err 时传播
Ok(contents.lines().next().unwrap_or("").to_string())
}
fn greet() -> Result<(), io::Error> {
let name = read_username()?; // ? = 如果 Err,立即返回 Err
println!("Hello, {name}!"); // 只在 Ok 时执行
Ok(())
}
// ? 说:"如果是 Err,立即从这个函数返回。"
// 它像 Python 的异常传播,但:
// 1. 可见(你看到 ?)
// 2. 在返回类型中(Result<..., io::Error>)
// 3. 编译器确保你在某处处理它
}
使用 ? 链式调用
# Python — 多个可能失败的操作
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() {
// Rust — 相同的链,但显式
fn process_file(path: &str) -> Result<Data, AppError> {
let text = fs::read_to_string(path)?; // ? 传播 io::Error
let data: Value = serde_json::from_str(&text)?; // ? 传播 serde 错误
let validated = validate(&data)?; // ? 传播验证错误
let result = transform(&validated)?; // ? 传播转换错误
Ok(result)
}
// 每个 ? 都是潜在的错误返回点 — 它们都可见!
}
flowchart TD
A["read_to_string(path)?"] -->|Ok| B["serde_json::from_str?"]
A -->|Err| X["返回 Err(io::Error)"]
B -->|Ok| C["validate(&data)?"]
B -->|Err| Y["返回 Err(serde::Error)"]
C -->|Ok| D["transform(&validated)?"]
C -->|Err| Z["返回 Err(ValidationError)"]
D -->|Ok| E["Ok(result) ✅"]
D -->|Err| W["返回 Err(TransformError)"]
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
每个
?都是一个错误出口点 — 而 Python 的 try/except 如果不阅读代码,你无法看出哪行可能抛出异常。📌 另见:第 15 章 — 迁移模式 涵盖在真实代码库中将 Python try/except 模式转换为 Rust。
使用 thiserror 的自定义错误类型
graph TD
AE["AppError(enum)"] --> 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"] -->|"通过 From 自动转换"| IO
JSON2["serde_json::Error"] -->|"通过 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
#[from]属性自动生成impl From<io::Error> for AppError,所以?自动将库错误转换为你的应用错误。
Python 自定义异常
# 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 使用 thiserror 的自定义错误
#![allow(unused)]
fn main() {
// Rust — 使用 thiserror 的错误枚举(最流行的方法)
// Cargo.toml: thiserror = "2"
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), // 从 io::Error 自动转换
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error), // 从 serde 错误自动转换
}
// 用法:
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,
})
}
// #[from] 属性意味着 ? 自动将 io::Error 转换为 AppError::Io
fn load_users(path: &str) -> Result<Vec<User>, AppError> {
let data = fs::read_to_string(path)?; // io::Error → AppError::Io 自动
let users: Vec<User> = serde_json::from_str(&data)?; // → AppError::Json
Ok(users)
}
}
错误处理快速参考
| Python | Rust | 说明 |
|---|---|---|
raise ValueError("msg") | return Err(AppError::Validation {...}) | 显式返回错误 |
try: ... except: | match result { Ok(v) => ..., Err(e) => ... } | 穷尽处理 |
except ValueError as e: | Err(AppError::Validation { .. }) => | 模式匹配 |
raise ... from e | #[from] 属性或 .map_err() | 错误链 |
finally: | Drop trait(自动) | 确定性清理 |
with open(...): | 基于作用域的 drop(自动) | RAII 模式 |
| 异常静默传播 | ? 显式传播 | 错误类型总是在返回类型中声明 |
isinstance(e, ValueError) | matches!(e, AppError::Validation {..}) | 错误类型检查 |
练习
🏋️ 练习:解析配置值(点击展开)
挑战:编写函数 parse_port(s: &str) -> Result<u16, String>,要求:
- 拒绝空字符串,错误为
"empty input" - 解析字符串为
u16,将解析错误映射为"invalid number: {original_error}" - 拒绝小于 1024 的端口,错误为
"port {n} is privileged"
用 ""、"hello"、"80" 和 "8080" 调用它并打印结果。
🔑 解答
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}"),
}
}
}
核心要点:? 与 .map_err() 是 Rust 替代 try/except ValueError as e: raise ConfigError(...) from e 的方案。
每个错误路径在返回类型中都可见。
特征与泛型
Traits vs 鸭子类型
你将学到: trait 作为显式契约(对比 Python 鸭子类型)、
Protocol(PEP 544) ≈ trait、 使用where子句的泛型约束、trait 对象 (dyn Trait) vs 静态分派,以及常见的 std trait。难度: 🟡 中级
这里是 Rust 类型系统真正让 Python 开发者眼前一亮的地方。Python 的“鸭子类型“说: “如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。“Rust 的 trait 说: “我会在编译时明确告诉你,我需要鸭子具备哪些行为。”
Python 鸭子类型
# Python — 鸭子类型:任何有正确方法的都能工作
def total_area(shapes):
"""适用于任何有 .area() 方法的对象。"""
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
# 运行时工作 —— 不需要继承!
shapes = [Circle(5), Rectangle(3, 4)]
print(total_area(shapes)) # 90.54
# 但如果某个对象没有 .area() 会怎样?
class Dog:
def bark(self): return "Woof!"
total_area([Dog()]) # 💥 AttributeError: 'Dog' has no attribute 'area'
# 错误发生在运行时,而不是定义时
Rust Trait —— 显式的鸭子类型
#![allow(unused)]
fn main() {
// Rust — trait 让"鸭子"契约变得明确
trait HasArea {
fn area(&self) -> f64; // 任何实现此 trait 的类型都有 .area()
}
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
}
}
// trait 约束是显式的 —— 编译器在编译时检查
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)); // 90.54
// struct Dog;
// total_area(&[&Dog {}]); // ❌ 编译错误:Dog 没有实现 HasArea
}
核心洞察:Python 的鸭子类型将错误推迟到运行时。Rust 的 trait 在编译时就能捕获它们。 同样的灵活性,但错误检测更早。
Protocol (PEP 544) vs Trait
Python 3.8 引入了 Protocol (PEP 544) 用于结构子类型 —— 这是最接近 Rust trait 的 Python 概念。
Python Protocol
# Python — Protocol(结构类型,类似 Rust trait)
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())
# 工作,因为 User 和 Product 都有 to_string()
print_all([User("Alice"), Product("Widget", 9.99)])
# 但是:mypy 会检查,Python 运行时不强制
# print_all([42]) # mypy 警告,但 Python 运行它并崩溃
Rust Trait(等价,但强制执行!)
#![allow(unused)]
fn main() {
// Rust — trait 在编译时强制执行
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]); // ❌ 编译错误:i32 没有实现 Printable
}
对比表
| 特性 | Python Protocol | Rust Trait |
|---|---|---|
| 结构类型 | ✅(隐式) | ❌(显式 impl) |
| 检查时机 | 运行时(或 mypy) | 编译时(始终) |
| 默认实现 | ❌ | ✅ |
| 可以给外部类型添加 | ❌ | ✅(有限制) |
| 多个 protocol | ✅ | ✅(多个 trait) |
| 关联类型 | ❌ | ✅ |
| 泛型约束 | ✅(使用 TypeVar) | ✅(trait 约束) |
泛型约束
Python 泛型
# Python — TypeVar 用于泛型函数
from typing import TypeVar, Sequence
T = TypeVar('T')
def first(items: Sequence[T]) -> T | None:
return items[0] if items else None
# 有边界的 TypeVar
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 带 Trait 约束的泛型
#![allow(unused)]
fn main() {
// Rust — 带 trait 约束的泛型
fn first<T>(items: &[T]) -> Option<&T> {
items.first()
}
// 带 trait 约束 —— "T 必须实现这些 trait"
fn average<T>(items: &[T]) -> f64
where
T: Into<f64> + Copy, // T 必须能转为 f64 且可复制
{
let sum: f64 = items.iter().map(|&x| x.into()).sum();
sum / items.len() as f64
}
// 多重约束 —— "T 必须实现 Display AND Debug AND Clone"
fn log_and_clone<T: std::fmt::Display + std::fmt::Debug + Clone>(item: &T) -> T {
println!("Display: {}", item);
println!("Debug: {:?}", item);
item.clone()
}
// 简写形式 impl Trait(用于简单情况)
fn print_it(item: &impl std::fmt::Display) {
println!("{}", item);
}
}
泛型快速参考
| Python | Rust | 说明 |
|---|---|---|
TypeVar('T') | <T> | 无边界泛型 |
TypeVar('T', bound=X) | <T: X> | 有边界泛型 |
Union[int, str] | enum 或 trait 对象 | Rust 没有 union 类型 |
Sequence[T] | &[T](slice) | 借用序列 |
Callable[[A], R] | Fn(A) -> R | 函数 trait |
Optional[T] | Option<T> | 内置于语言 |
常见标准库 Trait
这些是 Rust 版本的 Python “dunder 方法”(魔术方法)—— 它们定义类型在常见操作下的行为。
Display 和 Debug(打印)
#![allow(unused)]
fn main() {
use std::fmt;
// Debug —— 类似 __repr__(可自动推导)
#[derive(Debug)]
struct Point { x: f64, y: f64 }
// 现在可以:println!("{:?}", point);
// Display —— 类似 __str__(必须手动实现)
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// 现在可以:println!("{}", point);
}
比较 Trait
#![allow(unused)]
fn main() {
// PartialEq —— 类似 __eq__
// Eq —— 完全等价(f64 是 PartialEq 但不是 Eq,因为 NaN != NaN)
// PartialOrd —— 类似 __lt__、__le__ 等
// Ord —— 完全排序
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
struct Student {
name: String,
grade: i32,
}
// 现在 students 可以:比较、排序、用作 HashMap 键、克隆
let mut students = vec![
Student { name: "Charlie".into(), grade: 85 },
Student { name: "Alice".into(), grade: 92 },
];
students.sort(); // 使用 Ord —— 按姓名然后成绩排序(结构体字段顺序)
}
Iterator Trait
#![allow(unused)]
fn main() {
// 实现 Iterator —— 类似 Python 的 __iter__/__next__
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}"); // 5, 4, 3, 2, 1
}
}
常见 Trait 一览
| Rust Trait | Python 等价 | 用途 |
|---|---|---|
Display | __str__ | 人类可读字符串 |
Debug | __repr__ | 调试字符串(可推导) |
Clone | copy.deepcopy | 深拷贝 |
Copy | (int/float 自动复制) | 简单类型的隐式复制 |
PartialEq / Eq | __eq__ | 相等性比较 |
PartialOrd / Ord | __lt__ 等 | 排序 |
Hash | __hash__ | 可哈希(用于 dict 键) |
Default | 默认 __init__ | 默认值 |
From / Into | __init__ 重载 | 类型转换 |
Iterator | __iter__ / __next__ | 迭代 |
Drop | __del__ / __exit__ | 清理 |
Add, Sub, Mul | __add__, __sub__, __mul__ | 运算符重载 |
Index | __getitem__ | 使用 [] 索引 |
Deref | (无等价) | 智能指针解引用 |
Send / Sync | (无等价) | 线程安全标记 |
flowchart TB
subgraph Static ["静态分派 (impl Trait)"]
G["fn notify(item: &impl Summary)"] --> M1["编译:notify_Article()"]
G --> M2["编译:notify_Tweet()"]
M1 --> O1["内联,零成本"]
M2 --> O2["内联,零成本"]
end
subgraph Dynamic ["动态分派 (dyn Trait)"]
D["fn notify(item: &dyn Summary)"] --> VT["vtable 查找"]
VT --> I1["Article::summarize()"]
VT --> I2["Tweet::summarize()"]
end
style Static fill:#d4edda
style Dynamic fill:#fff3cd
Python 等价:Python 总是使用动态分派(运行时
getattr)。Rust 默认使用静态分派(单态化 —— 编译器为每个具体类型生成专用代码)。只有当你需要运行时多态时才使用dyn Trait。📌 另见:第 11 章 — From/Into Trait 深入讲解转换 trait(
From、Into、TryFrom)。
关联类型
Rust trait 可以定义关联类型 —— 这是每个实现者需要填充的类型占位符。Python 没有等价概念:
#![allow(unused)]
fn main() {
// Iterator 定义了一个关联类型 'Item'
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
struct Countdown { remaining: u32 }
impl Iterator for Countdown {
type Item = u32; // 这个迭代器产生 u32 值
fn next(&mut self) -> Option<u32> {
if self.remaining > 0 {
self.remaining -= 1;
Some(self.remaining)
} else {
None
}
}
}
}
在 Python 中,__iter__ / __next__ 返回 Any —— 没有办法声明“这个迭代器产生 int“并强制执行(即使使用 Iterator[int] 的类型提示也只是建议性的)。
运算符重载:__add__ → impl Add
Python 使用魔术方法(__add__、__mul__)。Rust 使用 trait 实现 —— 思路相同,但 Rust 在编译时进行类型检查:
# Python
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) # 'other' 上没有类型检查
#![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; // 类型安全:只允许 Vec2 + Vec2
}
关键区别:Python 的 __add__ 在运行时接受任何 other(你需要手动检查类型,否则会得到 TypeError)。Rust 的 Add trait 在编译时强制检查操作数类型 —— Vec2 + i32 是编译错误,除非你显式地 impl Add<i32> for Vec2。
练习
🏋️ 练习:泛型 Summary Trait(点击展开)
挑战:定义 trait Summary,带方法 fn summarize(&self) -> String。为两个结构体实现它:
Article { title: String, body: String } 和 Tweet { username: String, content: String }。
然后编写函数 fn notify(item: &impl Summary) 打印摘要。
🔑 解答
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);
}
核心要点:&impl Summary 是 Python 的 Protocol(带 summarize 方法)的 Rust 等价物。
但 Rust 在编译时检查 —— 传递没有实现 Summary 的类型会导致编译错误,而不是运行时的 AttributeError。
From 和 Into 特征
Rust 中的类型转换
你将学到:
From和Intotrait 用于零成本类型转换、TryFrom用于可能失败的转换、impl From<A> for B如何自动生成Into实现、以及字符串转换模式。难度: 🟡 中级
Python 使用构造函数调用处理类型转换(int("42")、str(42)、float("3.14"))。
Rust 使用 From 和 Into trait 进行类型安全的转换。
Python 类型转换
# Python —— 显式构造函数用于转换
x = int("42") # str → int(可能抛出 ValueError)
s = str(42) # int → str
f = float("3.14") # str → float
lst = list((1, 2, 3)) # tuple → list
# 通过 __init__ 或类方法进行自定义转换
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
#![allow(unused)]
fn main() {
// Rust —— From trait 定义转换
// 实现 From<T> 会自动获得 Into!
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)
}
}
// 现在两种写法都可以:
let c1 = Celsius::from(Fahrenheit(212.0)); // 显式 From
let c2: Celsius = Fahrenheit(212.0).into(); // Into(自动推导)
// 字符串转换:
let s: String = String::from("hello"); // &str → String
let s: String = "hello".to_string(); // 一样的
let s: String = "hello".into(); // 也可以(From 已实现)
let num: i64 = 42i32.into(); // i32 → i64(无损,所以存在 From)
// let small: i32 = 42i64.into(); // ❌ i64 → i32 可能丢失数据 —— 没有 From
// 对于可能失败的转换,使用 TryFrom:
let n: Result<i32, _> = "42".parse(); // str → i32(可能失败)
let n: i32 = "42".parse().unwrap(); // 如果解析失败则 panic
let n: i32 = "42".parse()?; // 使用 ? 传播错误
}
From/Into 关系
flowchart LR
A["impl From<A> for B"] -->|"自动生成"| B["impl Into<B> for A"]
C["Celsius::from(Fahrenheit(212.0))"] ---|"等同于"| D["Fahrenheit(212.0).into()"]
style A fill:#d4edda
style B fill:#d4edda
经验法则:始终优先实现
From,不要直接实现Into。只要实现了From<A> for B,就会自动获得Into<B> for A。
何时使用 From/Into
#![allow(unused)]
fn main() {
// 为你的类型实现 From<T>,打造符合人体工程学的 API:
#[derive(Debug)]
struct UserId(i64);
impl From<i64> for UserId {
fn from(id: i64) -> Self {
UserId(id)
}
}
// 现在函数可以接受任何可转换为 UserId 类型的值:
fn find_user(id: impl Into<UserId>) -> Option<String> {
let user_id = id.into();
// ... 查找逻辑
Some(format!("User #{:?}", user_id))
}
find_user(42i64); // ✅ i64 自动转换为 UserId
find_user(UserId(42)); // ✅ UserId 类型保持不变
}
TryFrom —— 可能失败的转换
并非所有转换都能成功。Python 会抛出异常;Rust 使用 TryFrom 返回 Result:
# Python —— 可能失败的转换抛出异常
try:
port = int("not_a_number") # ValueError
except ValueError as e:
print(f"Invalid: {e}")
# 在 __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
except ValueError:
pass
#![allow(unused)]
fn main() {
use std::num::ParseIntError;
// 内置类型的 TryFrom
let n: Result<i32, ParseIntError> = "42".try_into(); // Ok(42)
let n: Result<i32, ParseIntError> = "bad".try_into(); // Err(...) // 解析失败
// 自定义 TryFrom 用于验证
#[derive(Debug)]
struct Port(u16);
#[derive(Debug)]
enum PortError {
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)),
}
}
}
impl std::fmt::Display for PortError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
match self {
PortError::Zero => write!(f, "port cannot be zero"),
}
}
}
// 使用:
let p: Result<Port, _> = 8080u16.try_into(); // Ok(Port(8080))
let p: Result<Port, _> = 0u16.try_into(); // Err(PortError::Zero)
}
Python → Rust 思维模型转换:
TryFrom相当于进行验证且可能失败的__init__。但它返回Result而不是抛出异常 —— 所以调用者必须处理错误情况。
字符串转换的常见模式
字符串是 Python 开发者最容易混淆的转换来源:
#![allow(unused)]
fn main() {
// String → &str(借用,免费)
let s = String::from("hello");
let r: &str = &s; // 自动 Deref 转换
let r: &str = s.as_str(); // 显式
// &str → String(分配,消耗内存)
let r: &str = "hello";
let s1 = String::from(r); // From trait
let s2 = r.to_string(); // ToString trait(通过 Display)
let s3: String = r.into(); // Into trait
// 数字 → 字符串
let s = 42.to_string(); // "42" —— 类似于 Python 的 str(42)
let s = format!("{:.2}", 3.14); // "3.14" —— 类似于 Python 的 f"{3.14:.2f}"
// String → 数字
let n: i32 = "42".parse().unwrap(); // 类似 Python 的 int("42")
let f: f64 = "3.14".parse().unwrap(); // 类似 Python 的 float("3.14")
// 自定义类型 → String(实现 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) —— 类似于 Python 的 __str__
let s = p.to_string(); // 也可以!Display 免费给你 ToString
}
转换快速参考
| Python | Rust | 说明 |
|---|---|---|
str(x) | x.to_string() | 需要 Display 实现 |
int("42") | "42".parse::<i32>() | 返回 Result<T, E> |
float("3.14") | "3.14".parse::<f64>() | 返回 Result<T, E> |
list(iter) | iter.collect::<Vec<_>>() | 需要类型注解 |
dict(pairs) | pairs.collect::<HashMap<_,_>>() | 需要类型注解 |
bool(x) | 无直接等价 | 使用显式检查 |
MyClass(x) | MyClass::from(x) | 实现 From<T> |
MyClass(x)(验证) | MyClass::try_from(x)? | 实现 TryFrom<T> |
转换链与错误处理
真实世界的代码经常需要链式调用多个转换。对比两种处理方式:
# Python —— 使用 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}"),
}
}
核心洞察:每个
?都是一个可见的错误出口点。在 Python 中,try块内的任何一行都可能抛出异常 —— 在 Rust 中,只有以?结尾的行才会失败。📌 另见:第 9 章 —— 错误处理,深入讲解
Result、?运算符和使用thiserror的自定义错误类型。
练习
🏋️ 练习:温度转换库(点击展开)
挑战:构建一个小型温度转换库:
- 定义
Celsius(f64)、Fahrenheit(f64)和Kelvin(f64)结构体 - 实现
From<Celsius> for Fahrenheit和From<Celsius> for Kelvin - 实现
TryFrom<f64> for Kelvin,拒绝低于绝对零度的值(-273.15°C = 0K) - 为所有三种类型实现
Display(例如"100.00°C")
🔑 解答
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}"),
}
}
核心要点:From 处理不可失败的转换(Celsius→Fahrenheit 总是可行)。TryFrom 处理可能失败的转换(负 Kelvin 不可能)。Python 在 __init__ 中混同了这两种情况 —— Rust 则在类型系统中明确区分。
闭包与迭代器
Rust 闭包 vs Python Lambda
你将学到: 多行闭包(不只是单表达式 lambda)、
Fn/FnMut/FnOnce捕获语义、 迭代器链 vs 列表推导式、map/filter/fold,以及macro_rules!宏基础。难度: 🟡 中级
Python 闭包和 Lambda
# Python —— lambda 是单行表达式匿名函数
double = lambda x: x * 2
result = double(5) # 10
# 完整闭包从外层作用域捕获变量:
def make_adder(n):
def adder(x):
return x + n # 从外层作用域捕获 `n`
return adder
add_5 = make_adder(5)
print(add_5(10)) # 15
# 高阶函数:
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
Rust 闭包
#![allow(unused)]
fn main() {
// Rust —— 闭包使用 `|args| body` 语法
let double = |x: i32| x * 2;
let result = double(5); // 10
// 闭包从外层作用域捕获变量:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n // `move` 将 `n` 的所有权转移进闭包
}
let add_5 = make_adder(5);
println!("{}", add_5(10)); // 15
// 使用迭代器的高阶函数:
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();
}
闭包语法对比
Python: Rust:
───────── ─────
lambda x: x * 2 |x| x * 2
lambda x, y: x + y |x, y| x + y
lambda: 42 || 42
# 多行
def f(x): |x| {
y = x * 2 let y = x * 2;
return y + 1 y + 1
}
闭包捕获 —— Rust 的不同之处
# Python —— 闭包通过引用捕获(延迟绑定陷阱!)
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2] —— 没想到吧!都捕获了同一个 `i`
# 使用默认参数技巧修复:
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]
#![allow(unused)]
fn main() {
// Rust —— 闭包正确捕获(没有延迟绑定的陷阱)
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] —— 正确!
// `move` 为每个闭包捕获 `i` 的副本 —— 没有延迟绑定的陷阱。
}
三个闭包 Trait
#![allow(unused)]
fn main() {
// Rust 闭包实现以下一个或多个 trait:
// Fn —— 可多次调用,不改变捕获的变量(最常见)
fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(x) }
// FnMut —— 可多次调用,可能改变捕获的变量
fn apply_mut(mut f: impl FnMut(i32) -> i32, x: i32) -> i32 { f(x) }
// FnOnce —— 只能调用一次(消耗捕获的变量)
fn apply_once(f: impl FnOnce() -> String) -> String { f() }
// Python 没有直接等价物 —— Python 闭包总是类似于 Fn。
// 在 Rust 中,编译器自动决定使用哪个 trait。
}
迭代器 vs 生成器
Python 生成器
# Python —— 使用 yield 的生成器
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 惰性 —— 值按需计算
fib = fibonacci()
first_10 = [next(fib) for _ in range(10)]
# 生成器表达式 —— 类似惰性列表推导
squares = (x ** 2 for x in range(1000000)) # 无内存分配
first_5 = [next(squares) for _ in range(5)]
Rust 迭代器
#![allow(unused)]
fn main() {
// Rust —— Iterator trait(概念相似,语法不同)
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)
}
}
// 惰性 —— 值按需计算(与 Python 生成器一样)
let first_10: Vec<u64> = Fibonacci::new().take(10).collect();
// 迭代器链 —— 类似生成器表达式
let squares: Vec<u64> = (0..1_000_000u64).map(|x| x * x).take(5).collect();
}
推导式 vs 迭代器链
本节将 Python 的推导式语法与 Rust 的迭代器链进行对比。
列表推导 → map/filter/collect
# Python 推导式:
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["源<br/>[1,2,3,4,5]"] -->|".iter()"| B["迭代器"]
B -->|".filter(x: x%2==0)"| C["[2, 4]"]
C -->|".map(x: x*x)"| D["[4, 16]"]
D -->|".collect()"| E["Vec<i32><br/>[4, 16]"]
style A fill:#ffeeba
style E fill:#d4edda
核心洞察:Rust 迭代器是惰性的 —— 在调用
.collect()之前什么都不会发生。Python 的生成器也是类似的,但列表推导是立即求值的。
#![allow(unused)]
fn main() {
// Rust 迭代器链:
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();
}
字典推导 → 收集到 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();
}
集合推导 → 收集到 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();
}
常见迭代器方法
| Python | Rust | 说明 |
|---|---|---|
map(f, iter) | .map(f) | 转换每个元素 |
filter(f, iter) | .filter(f) | 保留匹配的元素 |
sum(iter) | .sum() | 求和所有元素 |
min(iter) / max(iter) | .min() / .max() | 返回 Option |
any(f(x) for x in iter) | .any(f) | 任一匹配则为 true |
all(f(x) for x in iter) | .all(f) | 全部匹配则为 true |
enumerate(iter) | .enumerate() | 索引 + 值 |
zip(a, b) | a.zip(b) | 配对元素 |
len(list) | .count()(消耗迭代器!)或 .len() | 计数元素 |
list(reversed(x)) | .rev() | 反向迭代 |
itertools.chain(a, b) | a.chain(b) | 连接迭代器 |
next(iter) | .next() | 获取下一个元素 |
next(iter, default) | .next().unwrap_or(default) | 带默认值 |
list(iter) | .collect::<Vec<_>>() | 具体化为集合 |
sorted(iter) | 收集后 .sort() | 无惰性排序迭代器 |
functools.reduce(f, iter) | .fold(init, f) 或 .reduce(f) | 累积 |
关键差异
Python 迭代器: Rust 迭代器:
───────────────── ──────────────
- 默认惰性(生成器) - 默认惰性(所有迭代器链)
- yield 创建生成器 - impl Iterator { fn next() }
- StopIteration 结束 - None 结束
- 只能消费一次 - 只能消费一次
- 无类型安全 - 完全类型安全
- 稍慢(解释器) - 零成本(编译掉)
为什么 Rust 需要宏
Python 没有宏系统 —— 它使用装饰器、元类和运行时内省进行元编程。 Rust 使用宏在编译时生成代码。
Python 元编程 vs Rust 宏
# Python —— 使用装饰器和元类进行元编程
from dataclasses import dataclass
from functools import wraps
@dataclass # 在导入时生成 `__init__`、`__repr__`、`__eq__` 方法
class Point:
x: float
y: float
# 自定义装饰器
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 宏和声明式宏进行代码生成
#[derive(Debug, Clone, PartialEq)] // 在编译时生成 `Debug`、`Clone`、`PartialEq` 实现
struct Point {
x: f64,
y: f64,
}
// 声明式宏(类似于模板)
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())
}
}
常见内置宏
#![allow(unused)]
fn main() {
// 这些宏在 Rust 中无处不在:
println!("Hello, {}!", name); // 带格式化打印
format!("Value: {}", x); // 创建格式化 String
vec![1, 2, 3]; // 创建 Vec
assert_eq!(2 + 2, 4); // 测试断言
assert!(value > 0, "must be positive"); // 布尔断言
dbg!(expression); // 调试打印:打印表达式和值
todo!(); // 占位符 —— 可编译但如果到达则 panic
unimplemented!(); // 标记代码为未实现
panic!("something went wrong"); // 崩溃并带消息(类似 raise RuntimeError)
// 为什么这些是宏而不是函数?
// - `println!` 接受可变参数(Rust 函数不能)
// - `vec!` 为任何类型和大小生成代码
// - `assert_eq!` 知道你比较的源代码
// - `dbg!` 知道文件名和行号
}
使用 macro_rules! 编写简单宏
#![allow(unused)]
fn main() {
// Python `dict()` 等价
// 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 宏 —— 自动实现 Trait
#![allow(unused)]
fn main() {
// `#[derive(...)]` 是 Python `@dataclass` 装饰器的 Rust 等价物
// 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,
}
// 常见 derive 宏:
// Debug → {:?} 格式化(类似 __repr__)
// Clone → .clone() 深拷贝
// Copy → 隐式复制(仅用于简单类型)
// PartialEq, Eq → == 比较(类似 __eq__)
// PartialOrd, Ord → <, >, 排序(类似 __lt__ 等)
// Hash → 可用作 HashMap 键(类似 __hash__)
// Default → MyType::default()(类似无参 __init__)
// Crate 提供的 derive 宏:
// Serialize, Deserialize (serde) → JSON/YAML/TOML 序列化
// (类似于 Python 的 `json.dumps`/`loads` 但类型安全)
}
Python 装饰器 vs Rust Derive
| Python 装饰器 | Rust Derive | 用途 |
|---|---|---|
@dataclass | #[derive(Debug, Clone, PartialEq)] | 数据类 |
@dataclass(frozen=True) | 默认不可变 | 不可变性 |
@dataclass(order=True) | #[derive(Ord, PartialOrd)] | 比较/排序 |
@total_ordering | #[derive(PartialOrd, Ord)] | 完整排序 |
JSON json.dumps(obj.__dict__) | #[derive(Serialize)] | 序列化 |
MyClass(**json.loads(s)) | #[derive(Deserialize)] | 反序列化 |
练习
🏋️ 练习:Derive 和自定义 Debug(点击展开)
挑战:创建一个 User 结构体,字段包括 name: String、email: String 和 password_hash: String。
派生 Clone 和 PartialEq,但手动实现 Debug,使其打印姓名和邮箱,但隐藏密码(显示 "***")。
🔑 解答
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:?}");
// 输出:User { name: "Alice", email: "alice@example.com", password_hash: "***" }
}
核心要点:与 Python 的 __repr__ 不同,Rust 可以免费派生 Debug —— 但你可以为敏感字段覆盖它。
这比 Python 更安全,因为在 Python 中 print(user) 可能意外泄露敏感信息。
并发编程
无 GIL:真正的并行
你将学到: 为什么 GIL 限制 Python 并发、Rust 的
Send/Synctrait 用于编译时线程安全、Arc<Mutex<T>>与 Pythonthreading.Lock的对比、channel 与queue.Queue的对比,以及 async/await 的差异。难度: 🔴 高级
GIL(全局解释器锁)是 Python CPU 密集型工作的最大瓶颈。 Rust 没有 GIL —— 线程真正并行运行,类型系统会在编译时防止数据竞争。
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
核心洞察:Python 线程对 CPU 密集型工作是顺序运行的(GIL 将它们序列化)。Rust 线程真正并行 —— 4 个线程 ≈ 4 倍加速。
📌 前提:在开始本章之前,确保你熟悉 第 7 章 —— 所有权与借用。
Arc、Mutex和move闭包都建立在所有权概念之上。
Python 的 GIL 问题
# Python —— 线程对 CPU 密集型工作没有帮助
import threading
import time
counter = 0
def increment(n):
global counter
for _ in range(n):
counter += 1 # 不是线程安全的!但 GIL"保护"了简单操作
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}") # 可能不是 4,000,000!
print(f"Time: {elapsed:.2f}s") # 和单线程差不多(GIL)
# 对于真正的并行,Python 需要使用多进程:
from multiprocessing import Pool
with Pool(4) as pool:
results = pool.map(cpu_work, data) # 分离进程,pickle 开销
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)); // 总是 4,000,000
// 在ALL核心上运行 —— 真正并行,无 GIL
}
线程安全:类型系统保证
Python —— 运行时错误
# Python —— 数据竞争在运行时被发现(或根本不被发现)
import threading
shared_list = []
def append_items(items):
for item in items:
shared_list.append(item) # 由于 GIL,`append` 是"线程安全"的
# 但复杂操作不安全:
# if item not in shared_list:
# shared_list.append(item) # 存在竞争条件!
# 使用 Lock 保证安全:
lock = threading.Lock()
def safe_append(items):
for item in items:
with lock:
if item not in shared_list:
shared_list.append(item)
# 忘记锁?没有编译器警告。bug 会在生产环境中被发现。
Rust —— 编译时错误
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// 尝试在没有保护的情况下跨线程共享 Vec:
// let shared = vec![];
// thread::spawn(move || shared.push(1));
// ❌ 编译错误:Vec 在没有保护的情况下不是 `Send`/`Sync`
// 使用 `Mutex`(Rust 的 `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(); // 访问需要锁
data.push(i);
// 当 `data` 超出作用域时锁自动释放
// 不会"忘记解锁" —— RAII 机制保证
})
}).collect();
for h in handles {
h.join().unwrap();
}
println!("{:?}", shared.lock().unwrap()); // [0, 1, 2, 3](顺序可能不同)
}
Send 和 Sync Trait
#![allow(unused)]
fn main() {
// Rust 使用两个标记 trait 来强制执行线程安全:
// `Send` —— "这个类型可以转移到另一个线程"
// 大多数类型是 `Send`。`Rc<T>` 不是(多线程使用 `Arc<T>`)。
// `Sync` —— "这个类型可以从多个线程引用"
// 大多数类型是 `Sync`。`Cell<T>`/`RefCell<T>` 不是(使用 `Mutex<T>`)。
// 编译器自动检查这些:
// thread::spawn(move || { ... })
// ↑ 闭包捕获的变量必须是 `Send`
// ↑ 共享引用必须是 `Sync`
// ↑ 如果不是 → 编译错误
// Python 没有等价物。线程安全 bug 在运行时才会被发现。
// Rust 在编译时捕获它们。这就是"无畏并发"。
}
并发原语对比
| Python | Rust | 用途 |
|---|---|---|
threading.Lock() | Mutex<T> | 互斥锁 |
threading.RLock() | Mutex<T>(不可重入) | 重入锁(使用方式不同) |
threading.RWLock(无) | RwLock<T> | 多读或一写 |
threading.Event() | Condvar | 条件变量 |
queue.Queue() | mpsc::channel() | 线程安全 channel |
multiprocessing.Pool | rayon::ThreadPool | 线程池 |
concurrent.futures | rayon / tokio::spawn | 基于任务的并行 |
threading.local() | thread_local! | 线程本地存储 |
| 无 | Atomic* 类型 | 无锁计数器和标志 |
Mutex 中毒
如果线程在持有 Mutex 时 panic,锁会变成中毒状态。Python 没有等价物 —— 如果线程在持有 threading.Lock() 时崩溃,锁会卡住导致死锁。
#![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!"); // 锁现在中毒了
}).join();
// 后续锁尝试返回 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]
}
}
}
原子序数(简要说明)
原子操作上的 Ordering 参数控制内存可见性保证:
| Ordering | 何时使用 |
|---|---|
Relaxed | 简单计数器,顺序不重要 |
Acquire/Release | 生产者 - 消费者:写者用 Release,读者用 Acquire |
SeqCst | 不确定时 —— 最严格的排序,最直观 |
Python 的 threading 模块将这些问题隐藏在 GIL 后面。在 Rust 中,你需要显式选择 —— 建议使用 SeqCst 直到性能分析显示你需要更弱的排序。
async/await 对比
Python 和 Rust 都有 async/await 语法,但它们在底层的工作方式非常不同。
Python async/await
# Python —— asyncio 用于并发 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 是单线程的(仍然受 GIL 限制)!
# 它只对 I/O 密集型工作有帮助(等待网络/磁盘)。
# async 中的 CPU 密集型工作仍然会阻塞事件循环。
Rust async/await
// Rust —— tokio 用于并发 I/O(也支持 CPU 并行!)
use reqwest;
use tokio;
use futures::future::join_all; // 添加 `futures` 到 Cargo.toml
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))) // 无 GIL 限制
.collect(); // 可以使用所有 CPU 核心
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(())
}
关键差异
| 方面 | Python asyncio | Rust tokio |
|---|---|---|
| GIL | 仍然受限 | 无 GIL |
| CPU 并行 | ❌ 单线程 | ✅ 多线程 |
| 运行时 | 内置 (asyncio) | 外部 crate (tokio) |
| 生态 | aiohttp, asyncpg 等 | reqwest, sqlx 等 |
| 性能 | I/O 良好 | I/O 和 CPU 都优秀 |
| 错误处理 | 异常 | Result<T, E> |
| 取消 | task.cancel() | Drop future |
| 颜色问题 | 同步 ↔ 异步边界 | 存在相同问题 |
使用 Rayon 的简单并行
# Python —— 使用多进程实现 CPU 并行
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 用于轻松的 CPU 并行(只需改一行!)
use rayon::prelude::*;
// 顺序:
let results: Vec<_> = items.iter().map(|item| heavy_computation(item)).collect();
// 并行(将 .iter() 改为 .par_iter() —— 仅此而已!):
let results: Vec<_> = items.par_iter().map(|item| heavy_computation(item)).collect();
// 无 pickle,无进程开销,无序列化。
// Rayon 自动跨核心分配工作。
}
💼 案例研究:并行图像处理管道
一个数据科学团队每晚处理 50,000 张卫星图像。他们的 Python 管道使用 multiprocessing.Pool:
# Python —— 多进程用于 CPU 密集型图像工作
import multiprocessing
from PIL import Image
import numpy as np
def process_image(path: str) -> dict:
img = np.array(Image.open(path))
# CPU 密集型:直方图均衡化、边缘检测、分类
histogram = np.histogram(img, bins=256)[0]
edges = detect_edges(img) # 每张图片 ~200ms
label = classify(edges) # 每张图片 ~100ms
return {"path": path, "label": label, "edge_count": len(edges)}
# 问题:每个子进程复制完整的 Python 解释器
# 内存:每个 worker 50MB × 16 个 worker = 800MB 开销
# 启动:fork 和 pickle 参数需要 2-3 秒
with multiprocessing.Pool(16) as pool:
results = pool.map(process_image, image_paths) # 50k 张图像 ~4.5 小时
痛点:fork 导致 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(无 numpy 开销)
let edges = detect_edges(&img); // ~40ms(SIMD 优化)
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 自动使用所有 CPU 核心 —— 无 fork、无 pickle、无 GIL
let results: Vec<ImageResult> = paths
.par_iter() // 并行迭代器
.filter_map(|p| process_image(p).ok()) // 优雅地跳过错误
.collect(); // 并行收集
println!("Processed {} images", results.len());
Ok(())
}
// 50k 张图像 ~35 分钟(对比 Python 的 4.5 小时)
// 内存:~50MB 总计(共享线程,无 fork)
结果:
| 指标 | Python (multiprocessing) | Rust (rayon) |
|---|---|---|
| 时间(50k 张图像) | ~4.5 小时 | ~35 分钟 |
| 内存开销 | 800MB(16 个 worker) | ~50MB(共享线程) |
| 错误处理 | 不透明的 pickle 错误 | 每步都有 Result<T, E> |
| 启动成本 | 2–3 秒(fork + pickle) | 无(线程) |
核心教训:对于 CPU 密集型并行工作,Rust 的线程 + rayon 可替代 Python 的
multiprocessing,零序列化开销、共享内存和编译时安全。
练习
🏋️ 练习:线程安全计数器(点击展开)
挑战:在 Python 中,你可能使用 threading.Lock 保护共享计数器。将其转换为 Rust:生成 10 个线程,每个线程递增共享计数器 1000 次。打印最终值(应该是 10000)。使用 Arc<Mutex<u64>>。
🔑 解答
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());
}
核心要点:Arc<Mutex<T>> 是 Python 的 lock = threading.Lock() + 共享变量的 Rust 等价物 —— 但如果你忘记 Arc 或 Mutex,Rust 不会编译。Python happily 运行有竞争的program并默默给你错误答案。
Unsafe Rust、FFI 与测试
何时以及为何使用 Unsafe
你将学到:
unsafe允许什么以及为什么存在、使用 PyO3 编写 Python 扩展(对 Python 开发者的杀手级特性)、 Rust 的测试框架与 pytest 的对比、使用 mockall 进行 mocking,以及基准测试。难度: 🔴 高级
Rust 中的 unsafe 是一个逃逸舱 —— 它告诉编译器“我在做一些你无法验证的事情,但我保证它是正确的。“
Python 没有等价物,因为 Python 从不给你直接内存访问的能力。
flowchart TB
subgraph Safe ["Safe Rust(99% 的代码)"]
S1["你的应用逻辑"]
S2["pub fn safe_api(&self) -> Result"]
end
subgraph Unsafe ["unsafe 块(最小化,已审计)"]
U1["原始指针解引用"]
U2["FFI 调用 C/Python"]
end
subgraph External ["外部(C / Python / 操作系统)"]
E1["libc / PyO3 / 系统调用"]
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
模式:安全 API 封装小的
unsafe块。调用者从不见到unsafe。Python 的ctypes没有这样的边界 —— 每个 FFI 调用隐式都是 unsafe 的。📌 另见:第 13 章 —— 并发 涵盖
Send/Synctrait,它们是编译器用于检查线程安全的自动 trait。
Unsafe 允许什么
// unsafe 允许你做五件 safe Rust 禁止的事情:
// 1. 解引用原始指针
// 2. 调用 unsafe 函数/方法
// 3. 访问可变静态变量
// 4. 实现 unsafe trait
// 5. 访问 union 的字段
// 示例:调用 C 函数
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
// SAFETY: abs() 是定义良好的 C 标准库函数
let result = unsafe { abs(-42) }; // Safe Rust 无法验证 C 代码
println!("{result}"); // 42
}
何时使用 Unsafe
#![allow(unused)]
fn main() {
// 1. FFI —— 调用 C 库(最常见原因)
// 2. 性能关键的内部循环(罕见)
// 3. 借用检查器无法表达的数据结构(罕见)
// 作为 Python 开发者,你主要会在以下地方遇到 unsafe:
// - PyO3 内部(Python ↔ Rust 桥接)
// - C 库绑定
// - 低级系统调用
// 经验法则:如果你在编写应用代码(而不是库代码),
// 几乎从不需要 unsafe。如果你认为需要,先在 Rust 社区询问 ——
// 通常有安全的替代方案。
}
PyO3:用于 Python 的 Rust 扩展
PyO3 是 Python 和 Rust 之间的桥梁。它让你编写可从 Python 调用的 Rust 函数和类 —— 非常适合替换缓慢的 Python 热点代码。
创建 Python 扩展
# 设置
pip install maturin # 用于编写 Rust Python 扩展的构建工具
maturin init # 创建项目结构
# 项目结构:
# 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"] # 用于 Python 的动态链接库
[dependencies]
pyo3 = { version = "0.22", features = ["extension-module"] }
#![allow(unused)]
fn main() {
// src/lib.rs —— 可从 Python 调用的 Rust 函数
use pyo3::prelude::*;
/// 一个快速的 Fibonacci 函数,用 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
}
/// 找出所有不超过 n 的质数(埃拉托斯特尼筛法)。
#[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()
}
/// 一个可供 Python 使用的 Rust 类。
#[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)
}
}
/// 定义 Python 模块。
#[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(())
}
}
从 Python 使用
# 构建和安装:
maturin develop --release # 构建并安装到当前的 Python 虚拟环境
# Python —— 像任何 Python 模块一样使用 Rust 扩展
import my_extension
# 调用 Rust 函数
result = my_extension.fibonacci(50)
print(result) # 12586269025 —— 微秒级计算完成
# 使用 Rust 类
counter = my_extension.Counter(0)
counter.increment()
counter.increment()
print(counter.get_value()) # 2
print(counter) # Counter(value=2)
# 性能对比:
import time
# Python 版本
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.5 秒
print(f"Rust: {rs_time:.3f}s") # ~0.05 秒 —— 70 倍更快!
print(f"Same results: {py_result == rs_result}") # True
PyO3 快速参考
| Python 概念 | PyO3 属性 | 说明 |
|---|---|---|
| 函数 | #[pyfunction] | 暴露给 Python |
| 类 | #[pyclass] | Python 可见的类 |
| 方法 | #[pymethods] | pyclass 上的方法 |
__init__ | #[new] | 构造函数 |
__repr__ | fn __repr__() | 字符串表示 |
__str__ | fn __str__() | 显示字符串 |
__len__ | fn __len__() | 长度 |
__getitem__ | fn __getitem__() | 索引 |
| 属性 | #[getter] / #[setter] | 属性访问 |
| 静态方法 | #[staticmethod] | 无 self |
| 类方法 | #[classmethod] | 接收 cls |
FFI 安全模式
当通过 PyO3 或原始 C FFI 将 Rust 暴露给 Python 时,遵循这些规则可以防止最常见的 bug:
-
永远不要让 panic 跨越 FFI 边界 —— Rust panic 展开到 Python(或 C)是未定义行为。PyO3 会自动为
#[pyfunction]处理这个问题,但原始extern "C"函数需要显式保护:#![allow(unused)] fn main() { #[no_mangle] pub extern "C" fn raw_ffi_function() -> i32 { match std::panic::catch_unwind(|| { // 实际逻辑 42 }) { Ok(result) => result, Err(_) => -1, // 返回错误码而不是 panic 进入 C/Python 代码 } } } -
共享结构体使用
#[repr(C)]—— 如果 Python/C 代码直接读取结构体字段,你必须使用#[repr(C)]保证 C 兼容布局。如果你传递不透明指针(PyO3 为#[pyclass]做的),则不需要。 -
extern "C"—— 原始 FFI 函数必需,这样调用约定才能与 C/Python 期望的匹配。PyO3 的#[pyfunction]为你处理这个。
PyO3 优势:PyO3 为你封装了这些安全问题中的大多数 —— panic 捕获、类型转换、GIL 管理。除非你有特定理由,否则优先使用 PyO3 而不是原始 FFI。
单元测试与 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)
# 参数化测试
@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
# Fixture
@pytest.fixture
def sample_data():
return [1, 2, 3, 4, 5]
def test_sum(sample_data):
assert sum(sample_data) == 15
# 运行测试
pytest # 运行所有测试
pytest test_calculator.py # 运行一个文件
pytest -k "test_add" # 运行匹配的测试
pytest -v # 详细输出
pytest --tb=short # 简短 traceback
Rust 内置的测试
#![allow(unused)]
fn main() {
// src/calculator.rs —— 测试与代码在同一个文件中!
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)
}
}
// 测试放在 #[cfg(test)] 模块中 —— 只在 `cargo test` 期间编译
#[cfg(test)]
mod tests {
use super::*; // 导入父模块中的所有内容
#[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());
}
// 测试某些操作是否 panic(类似于 pytest.raises)
#[test]
#[should_panic(expected = "out of bounds")]
fn test_out_of_bounds() {
let v = vec![1, 2, 3];
let _ = v[99]; // Panic
}
}
}
# 运行测试
cargo test # 运行所有测试
cargo test test_add # 运行匹配的测试
cargo test -- --nocapture # 显示 println! 输出
cargo test -p my_crate # 测试工作区中的一个 crate
cargo test -- --test-threads=1 # 顺序(用于有副作用的测试)
测试快速参考表
| pytest | Rust | 说明 |
|---|---|---|
assert x == y | assert_eq!(x, y) | 相等 |
assert x != y | assert_ne!(x, y) | 不等 |
assert condition | assert!(condition) | 布尔 |
assert condition, "msg" | assert!(condition, "msg") | 带消息的断言 |
pytest.raises(E) | #[should_panic] | 期望 panic 发生 |
@pytest.fixture | 在测试或辅助函数中设置 | Rust 无内置 fixture |
@pytest.mark.parametrize | rstest crate | 参数化测试 |
conftest.py | tests/common/mod.rs | 共享测试辅助 |
pytest.skip() | #[ignore] | 跳过测试 |
tmp_path fixture | tempfile crate | 临时目录 |
使用 rstest 进行参数化测试
#![allow(unused)]
fn main() {
// Cargo.toml: rstest = "0.23"
use rstest::rstest;
// 类似于 @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);
}
// 类似于 @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);
}
}
使用 mockall 进行 Mock 测试
# Python —— 使用 unittest.mock 进行 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 —— 使用 mockall crate 进行 Mock 测试
// Cargo.toml: mockall = "0.13"
use mockall::{automock, predicate::*};
#[automock] // 自动生成 MockDatabase
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()));
}
}
练习
🏋️ 练习:Unsafe 周围的安全包装器(点击展开)
挑战:编写一个安全函数 split_at_mid,接收 &mut [i32] 并返回两个可变切片 (&mut [i32], &mut [i32]),在中点分割。内部使用带原始指针的 unsafe 块(模拟 split_at_mut 的操作)。然后将其包装在安全 API 中。
🔑 解答
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); // unsafe 之前的安全检查
// SAFETY: mid <= len(上面已断言),并且 ptr 来自有效的 &mut 切片,
// 所以两个子切片都在边界内且不重叠。
unsafe {
(
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]
}
核心要点:unsafe 块很小并由 assert! 保护。公共 API 完全安全 —— 调用者从不见到 unsafe。这是 Rust 模式:内部 unsafe,外部安全接口。Python 的 ctypes 不给你这样的保证。
迁移模式
Python 常见模式的 Rust 实现
你将学到: 如何将 dict 转换为 struct、class 转换为 struct+impl、列表推导式转换为迭代器链、 decorator 转换为 trait、context manager 转换为 Drop/RAII。以及必备 crate 和渐进式采用策略。
难度: 🟡 中级
字典转换为结构体
# Python —— 使用 dict 作为数据容器(非常常见)
user = {
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"active": True,
}
print(user["name"])
#![allow(unused)]
fn main() {
// Rust —— 使用带命名字段的结构体
#[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);
}
Context Manager 转换为 RAII (Drop)
# Python —— context manager 用于资源清理
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")
# 退出 `with` 时文件自动关闭
#![allow(unused)]
fn main() {
// Rust —— RAII 模式:值超出作用域时 Drop trait 自动运行
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` 超出作用域时文件自动关闭
// 不需要 `with` —— RAII 自动处理!
}
}
Decorator 转换为高阶函数或宏
# Python —— 用于计时的 decorator(装饰器)
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 —— 没有 decorator 语法,使用 wrapper 函数或宏
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
});
}
迭代器管道(数据处理)
# Python —— 链式数据处理
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 —— 带强类型安全检查的迭代器链
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
}
}
全局配置 / Singleton 模式
# Python —— 模块级的 singleton(常见模式)
# config.py
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() # 模块级的 singleton 实例
#![allow(unused)]
fn main() {
// Rust —— 使用 OnceLock 实现延迟静态初始化(Rust 1.70+)
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();
}
Python 开发者必备 Crate 清单
数据处理与序列化
| 任务 | Python | Rust Crate | 说明 |
|---|---|---|---|
| JSON | json | serde_json | 类型安全的序列化 |
| CSV | csv, pandas | csv | 流式处理,低内存 |
| YAML | pyyaml | serde_yaml | 配置文件 |
| TOML | tomllib | toml | 配置文件 |
| 数据验证 | pydantic | serde + 自定义逻辑 | 编译时验证 |
| 日期/时间 | datetime | chrono | 完整的时区支持 |
| 正则 | re | regex | 非常快 |
| UUID | uuid | uuid | 相同的 API |
Web 与网络
| 任务 | Python | Rust Crate | 说明 |
|---|---|---|---|
| HTTP 客户端 | requests | reqwest | Async 优先 |
| Web 框架 | FastAPI/Flask | axum / actix-web | 非常快 |
| WebSocket | websockets | tokio-tungstenite | Async |
| gRPC | grpcio | tonic | 完整支持 |
| 数据库 (SQL) | sqlalchemy | sqlx / diesel | 编译时检查 SQL |
| Redis | redis-py | redis | Async 支持 |
CLI 与系统
| 任务 | Python | Rust Crate | 说明 |
|---|---|---|---|
| CLI 参数 | argparse/click | clap | Derive 宏 |
| 彩色输出 | colorama | colored | 终端彩色输出 |
| 进度条 | tqdm | indicatif | 类似的用户体验 |
| 文件监控 | watchdog | notify | 跨平台支持 |
| 日志 | logging | tracing | 结构化,async 就绪 |
| 环境变量 | os.environ | std::env + dotenvy | 支持 .env 文件 |
| 子进程 | subprocess | std::process::Command | 内置 |
| 临时文件 | tempfile | tempfile | 相同的 crate 名称! |
测试
| 任务 | Python | Rust Crate | 说明 |
|---|---|---|---|
| 测试框架 | pytest | 内置 + rstest | cargo test |
| Mocking | unittest.mock | mockall | 基于 trait |
| 属性测试 | hypothesis | proptest | 相似的 API |
| 快照测试 | syrupy | insta | 快照审批 |
| 基准测试 | pytest-benchmark | criterion | 统计学 |
| 代码覆盖率 | coverage.py | cargo-tarpaulin | 基于 LLVM |
渐进式采用 Rust 的策略
flowchart LR
A["1️⃣ Profile Python<br/>(查找热点)"] --> B["2️⃣ 编写 Rust 扩展模块<br/>(PyO3 + maturin)"]
B --> C["3️⃣ 替换 Python 调用<br/>(保持相同的 API)"]
C --> D["4️⃣ 渐进式扩展<br/>(更多函数)"]
D --> E{"完全用 Rust 重写<br/>值得吗?"}
E -->|是 | F["Pure Rust🦀"]
E -->|否 | 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
📌 另见:第 14 章 — Unsafe Rust 和 FFI 涵盖了 PyO3 绑定所需的底层 FFI 细节。
步骤 1:识别热点
步骤 1:识别热点
# 首先 profile(剖析)你的 Python 代码
import cProfile
cProfile.run('main()') # 查找 CPU 密集型函数
# 或使用 py-spy 进行采样分析:
# py-spy top --pid <python-pid>
# py-spy record -o profile.svg -- python main.py
步骤 2:为热点函数编写 Rust 扩展
# 使用 maturin 创建 Rust 扩展
cd my_python_project
maturin init --bindings pyo3
# 用 Rust 编写热点函数(参见前文的 PyO3 部分)
# 构建并安装:
maturin develop --release
步骤 3:将 Python 调用替换为 Rust 实现
# 之前:
result = python_hot_function(data) # 慢
# 之后:
import my_rust_extension
result = my_rust_extension.hot_function(data) # 快!
# 相同的 API,相同的测试,10-100 倍的性能提升
步骤 4:渐进式扩展
#![allow(unused)]
fn main() {
第 1-2 周:用一个 Rust 函数替换一个 CPU 绑定函数
第 3-4 周:替换数据解析/验证层
第 2 个月:替换核心数据管道
第 3 个月+:如果收益合理则考虑完全用 Rust 重写
关键原则:用 Python 做编排,用 Rust 做计算。
}
💼 案例研究:使用 PyO3 加速数据处理管道
一家金融科技公司有一个 Python 数据处理管道,每天处理 2GB 的交易 CSV 文件。关键瓶颈是验证和转换步骤:
# Python —— 慢的部分(处理 2GB 文件约 12 分钟)
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"]) # 字符串匹配,约 50 条规则
results.append({
"amount_cents": int(amount * 100),
"date": date.isoformat(),
"category": category,
"merchant": row["merchant"].strip().lower(),
})
return results
# 1500 万行约 12 分钟。尝试过 pandas —— 降到约 8 分钟但需要 6GB RAM。
步骤 1:Profile 并识别热点(CSV 解析 + Decimal 转换 + 字符串匹配 = 95% 的时间消耗)。
步骤 2:编写 Rust 扩展:
#![allow(unused)]
fn main() {
// src/lib.rs —— PyO3 扩展模块
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 {
// Aho-Corasick 算法或简单规则 —— 编译一次,运行非常快
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)?; // 自定义解析器(不需要 Decimal 模块)
let date = &record[1]; // 已经是 ISO 格式,只需验证即可
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(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(process_transactions, m)?)?;
Ok(())
}
}
步骤 3:在 Python 代码中只替换一行:
# 之前:
results = validate_and_transform("transactions.csv") // 12 分钟
# 之后:
import fast_pipeline
results = fast_pipeline.process_transactions("transactions.csv") // 45 秒
# 相同的 Python 编排逻辑,相同的测试用例,相同的部署方式
# 只替换了一个函数调用
结果:
| 指标 | Python (csv + Decimal) | Rust (PyO3 + csv crate) |
|---|---|---|
| 时间(2GB / 1500 万行) | 12 分钟 | 45 秒 |
| 峰值内存 | 6GB (pandas) / 2GB (csv) | 200MB |
| Python 中修改的行数 | — | 1 行(import + 调用) |
| 编写的 Rust 代码 | — | 约 60 行 |
| 测试通过 | 47/47 | 47/47(不变) |
核心教训:你不需要重写整个应用。找到占 95% 时间的 5% 代码,用 PyO3 将其重写为 Rust,其他部分保持 Python 不变。团队从“我们需要添加更多服务器“变成了“一台服务器就够了“。
练习
🏋️ 练习:迁移决策矩阵(点击展开)
挑战:你有一个 Python Web 应用,包含以下组件。对于每个组件,决定:保留 Python、用 Rust 重写或使用 PyO3 桥接。说明每个选择的理由。
- Flask 路由处理器(请求解析、JSON 响应)
- 图像缩略图生成(CPU 绑定,每天处理 10k 张图像)
- 数据库 ORM 查询(SQLAlchemy)
- 2GB 金融文件的 CSV 解析器(每夜运行)
- 管理后台仪表板(Jinja2 模板)
🔑 解答
| 组件 | 决策 | 理由 |
|---|---|---|
| Flask 路由处理器 | 🐍 保留 Python | I/O 绑定、框架依赖重、Rust 收益低 |
| 图像缩略图生成 | 🦀 PyO3 桥接 | CPU 绑定热点,保留 Python API,Rust 内部实现 |
| 数据库 ORM 查询 | 🐍 保留 Python | SQLAlchemy 成熟,查询是 I/O 绑定 |
| CSV 解析器(2GB) | 🦀 PyO3 桥接或完全 Rust | CPU + 内存绑定,Rust 的零拷贝解析优势明显 |
| 管理后台仪表板 | 🐍 保留 Python | UI/模板代码,无性能问题 |
核心要点:迁移的最佳目标是具有清晰边界的 CPU 密集型、性能关键代码。不要重写胶水代码或 I/O 绑定处理器——收益不足以证明成本合理。
最佳实践
Python 开发者的地道 Rust 实践指南
你将学到: 需要养成的 10 大习惯、常见陷阱及修复方法、结构化的 3 个月学习路径、 完整的 Python→Rust “罗塞塔石碑” 对照表,以及推荐的学习资源。
难度: 🟡 中级
flowchart LR
A["🟢 第 1-2 周<br/>基础<br/>'为什么这不能编译?'"] --> B["🟡 第 3-4 周<br/>核心概念<br/>'哦,它在保护我'"]
B --> C["🟡 第 2 个月<br/>中级<br/>'我明白为什么这很重要了'"]
C --> D["🔴 第 3 个月+<br/>高级<br/>'编译时就抓住了 bug!'"]
D --> E["🏆 第 6 个月<br/>流利<br/>'在任何语言上都是更好的程序员'"]
style A fill:#d4edda
style B fill:#fff3cd
style C fill:#fff3cd
style D fill:#f8d7da
style E fill:#c3e6cb,stroke:#28a745
需要养成的 10 大习惯
-
在 enum 上使用
match而不是if isinstance()# Python # Rust if isinstance(shape, Circle): ... match shape { Shape::Circle(r) => ... } -
让编译器指导你 —— 仔细阅读错误信息。Rust 的编译器是所有语言中最好的。 它会告诉你哪里错了以及如何修复。
-
函数参数优先使用
&str而不是String—— 接受最通用的类型。&str可以兼容String和字符串字面量。 -
使用迭代器而不是索引循环 —— 迭代器链比
for i in 0..vec.len()更地道, 通常性能也更好。 -
拥抱
Option和Result—— 不要对所有东西都用.unwrap()。 使用?、map、and_then、unwrap_or_else。 -
自由地使用 derive ——
#[derive(Debug, Clone, PartialEq)]应该出现在 大多数结构体上。这是零成本的,让测试更简单。 -
严格使用
cargo clippy—— 它捕获数百个风格和正确性问题。 把它当作 Rust 版的ruff。 -
不要与 borrow checker 对抗 —— 如果你在与它对抗,可能是你结构化数据的方式不对。 重构代码让所有权更清晰。
-
使用 enum 表示状态机 —— 不要使用字符串标志或布尔值,使用 enum。 编译器确保你处理了每种状态。
-
先 clone,稍后优化 —— 学习时,自由使用
.clone()来避免所有权的复杂性。 只有在性能剖析显示需要时才优化。
Python 开发者常犯的错误
| 错误 | 原因 | 修复 |
|---|---|---|
到处 .unwrap() | 运行时 panic | 使用 ? 或 match |
用 String 而不是 &str | 不必要的分配 | 参数使用 &str |
for i in 0..vec.len() | 不地道 | for item in &vec |
| 忽略 clippy 警告 | 错过简单的改进 | cargo clippy |
过多的 .clone() 调用 | 性能开销 | 重构所有权 |
巨大的 main() 函数 | 难以测试 | 提取到 lib.rs |
不使用 #[derive()] | 重复造轮子 | derive 常见 trait |
| 错误时 panic | 不可恢复的错误处理 | 返回 Result<T, E> |
性能对比
基准测试:常见操作
操作 Python 3.12 Rust (release) 加速比
───────────────────── ──────────── ────────────── ─────────
Fibonacci(40) ~25 秒 ~0.3 秒 ~80 倍
排序 1000 万整数 ~5.2 秒 ~0.6 秒 ~9 倍
JSON 解析 100MB ~8.5 秒 ~0.4 秒 ~21 倍
正则 100 万次匹配 ~3.1 秒 ~0.3 秒 ~10 倍
HTTP 服务器 (请求/秒) ~5,000 ~150,000 ~30 倍
SHA-256 1GB 文件 ~12 秒 ~1.2 秒 ~10 倍
CSV 解析 100 万行 ~4.5 秒 ~0.2 秒 ~22 倍
字符串拼接 ~2.1 秒 ~0.05 秒 ~42 倍
注意:使用 C 扩展的 Python(如 NumPy)会显著缩小数值计算的差距。 这些基准测试对比的是纯 Python 与纯 Rust。
内存使用
Python: Rust:
───────── ─────
- 对象头:28 字节/对象 - 无对象头
- int:28 字节(即使是 0) - i32:4 字节,i64:8 字节
- str "hello":54 字节 - &str "hello":16 字节(指针 + 长度)
- 1000 个整数的 list:~36 KB - Vec<i32>:~4 KB
(8 KB 指针 + 28 KB 整数对象)
- 100 个条目的 dict:~5.5 KB - 100 个条目的 HashMap:~2.4 KB
典型应用的总内存:
- Python:50-200 MB 基线 - Rust:1-5 MB 基线
常见陷阱及解决方案
陷阱 1:“Borrow Checker 不允许我这样做”
#![allow(unused)]
fn main() {
// 问题:尝试在迭代时修改
let mut items = vec![1, 2, 3, 4, 5];
// for item in &items {
// if *item > 3 { items.push(*item * 2); } // ❌ 不能在借用的同时进行可变借用
// }
// 解决方案 1:收集更改,之后应用
let additions: Vec<i32> = items.iter()
.filter(|&&x| x > 3)
.map(|&x| x * 2)
.collect();
items.extend(additions);
// 解决方案 2:使用 retain/extend
items.retain(|&x| x <= 3);
}
陷阱 2:“字符串类型太多了”
#![allow(unused)]
fn main() {
// 不确定时:
// - 函数参数使用 &str
// - 结构体字段和返回值使用 String
// - &str 字面量("hello")在任何需要 &str 的地方都可用
fn process(input: &str) -> String { // 接受 &str,返回 String
format!("Processed: {}", input)
}
}
陷阱 3:“我想念 Python 的简洁”
#![allow(unused)]
fn main() {
// Python 单行:
// result = [x**2 for x in data if x > 0]
// Rust 等价:
let result: Vec<i32> = data.iter()
.filter(|&&x| x > 0)
.map(|&x| x * x)
.collect();
// 它更冗长,但是:
// - 编译时类型安全
// - 10-100 倍的性能提升
// - 不可能出现运行时类型错误
// - 显式内存分配(.collect())
}
陷阱 4:“我的 REPL 在哪里?”
#![allow(unused)]
fn main() {
// Rust 没有 REPL。取而代之:
// 1. 使用 `cargo test` 作为 REPL —— 编写小测试来尝试东西
// 2. 使用 Rust Playground(play.rust-lang.org)进行快速实验
// 3. 使用 `dbg!()` 宏进行快速调试输出
// 4. 使用 `cargo watch -x test` 在保存时自动运行测试
#[test]
fn playground() {
// 把它当作你的 "REPL" —— 使用 `cargo test playground` 运行
let result = "hello world"
.split_whitespace()
.map(|w| w.to_uppercase())
.collect::<Vec<_>>();
dbg!(&result); // 打印:[src/main.rs:5] &result = ["HELLO", "WORLD"]
}
}
学习路径和资源
第 1-2 周:基础
- 安装 Rust,配置 VS Code 和 rust-analyzer
- 完成本指南的第 1-4 章(类型、控制流)
- 编写 5 个小程序,将 Python 脚本转换为 Rust
- 熟悉
cargo build、cargo test、cargo clippy
第 3-4 周:核心概念
- 完成第 5-8 章(struct、enum、所有权、模块)
- 用 Rust 重写一个 Python 数据处理脚本
- 练习使用
Option<T>和Result<T, E>直到自然 - 仔细阅读编译器错误信息 —— 它们在教你
第 2 个月:中级
- 完成第 9-12 章(错误处理、trait、迭代器)
- 使用
clap和serde构建一个 CLI 工具 - 为 Python 项目的热点函数编写 PyO3 扩展
- 练习迭代器链直到感觉像推导式
第 3 个月:高级
- 完成第 13-16 章(并发、unsafe、测试)
- 使用
axum和tokio构建一个 Web 服务 - 为开源 Rust 项目做贡献
- 阅读《Programming Rust》(O’Reilly)获得更深入的理解
推荐资源
- The Rust Book: https://doc.rust-lang.org/book/(官方教程,优秀)
- Rust by Example: https://doc.rust-lang.org/rust-by-example/(从做中学)
- Rustlings: https://github.com/rust-lang/rustlings(练习项目)
- Rust Playground: https://play.rust-lang.org/(在线编译器)
- This Week in Rust: https://this-week-in-rust.org/(每周通讯)
- PyO3 Guide: https://pyo3.rs/(Python ↔ Rust 桥接指南)
- Comprehensive Rust (Google): https://google.github.io/comprehensive-rust/
Python → Rust 罗塞塔石碑
| Python | Rust | 章节 |
|---|---|---|
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 | 泛型 <T> | 10 |
__dunder__ 方法 | Traits (Display, Add 等) | 10 |
lambda | |args| body | 12 |
generator yield | impl Iterator | 12 |
| list comprehension | .map().filter().collect() | 12 |
@decorator | 高阶函数或宏 | 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 (context manager) | 基于作用域的 Drop | 15 |
json.dumps/loads | serde_json | 15 |
给 Python 开发者的最终思考
#![allow(unused)]
fn main() {
你会怀念 Python 的:
- REPL 和交互式探索
- 快速原型速度
- 丰富的 ML/AI 生态系统(如 PyTorch)
- "Just works" 的动态类型
- pip install 和立即使用
你会从 Rust 获得的:
- "如果能编译,就能工作" 的信心
- 10-100 倍的性能提升
- 不再有运行时类型错误
- 不再有 None/null 崩溃
- 真正的并行(无 GIL!)
- 单一二进制部署
- 可预测的内存使用
- 所有语言中最好的编译器错误信息
旅程:
第 1 周: "为什么编译器跟我过不去?"
第 2 周: "哦,它实际上在保护我避免 bug"
第 1 个月:"我明白为什么这很重要了"
第 2 个月:"我在编译时抓住了一个 bug,否则会是生产事故"
第 3 个月:"我不想回到无类型代码了"
第 6 个月:"Rust 让我在任何语言上都成为了更好的程序员"
}
练习
🏋️ 练习:代码审查清单(点击展开)
挑战:审查这段 Rust 代码(由 Python 开发者编写)并找出 5 个可以改进的地方:
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);
}
🔑 解答
五个改进:
// 1. 接受 &[String] 而不是 Vec<String>(不要获取整个 vec 的所有权)
// 2. 使用 usize 作为索引(不是 i32 —— 索引总是非负的)
// 3. 返回 Option<&str> 而不是空字符串(使用类型系统!)
// 4. 使用 .get() 而不是手动边界检查
// 5. 不要在 main 中 clone() —— 传递引用
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"),
}
}
核心要点:Python 习惯在 Rust 中有害的地方:过度使用 clone(应使用借用)、使用哨兵值如 ""(应使用 Option)、在借用足够时获取所有权、对索引使用有符号整数。
Rust for Python Programmers 培训指南 完
综合项目:CLI 任务管理器
综合项目:构建 CLI 任务管理器
你将学到: 通过构建一个完整的 Rust CLI 应用来综合运用课程中的所有知识, 这种应用 Python 开发者通常会用
argparse+json+pathlib来编写。难度: 🔴 高级
这个综合项目练习了来自每个主要章节的概念:
- 第 3 章:类型和变量(struct、enum)
- 第 5 章:集合(
Vec、HashMap) - 第 6 章:enum 和模式匹配(任务状态、命令)
- 第 7 章:所有权和借用(传递引用)
- 第 9 章:错误处理(
Result、?、自定义错误) - 第 10 章:trait(
Display、FromStr) - 第 11 章:类型转换(
From、TryFrom) - 第 12 章:迭代器和闭包(过滤、映射)
- 第 8 章:模块(有组织的项目结构)
项目:rustdo
一个命令行任务管理器(类似 Python 的 todo.txt 工具),将任务存储在 JSON 文件中。
Python 等价实现(你会用 Python 写的)
#!/usr/bin/env python3
"""一个简单的 CLI 任务管理器 —— Python 版本。"""
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))
# 命令: add, list, done, remove, stats
# ...(你知道 Python 中怎么做)
你的 Rust 实现
逐步构建这个项目。每个步骤映射到特定章节的概念。
步骤 1:定义数据模型(第 3、6、10、11 章)
#![allow(unused)]
fn main() {
// src/task.rs
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use chrono::Local;
/// 任务优先级 —— 映射到 Python 的 Priority(Enum)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Priority {
Low,
Medium,
High,
}
// Display trait(Python 的 __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(解析 "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)")),
}
}
}
/// 单个任务 —— 映射到 Python 的 Task 类
#[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 对比:在 Python 中你会使用
@dataclass+Enum。在 Rust 中,struct+enum+derive宏免费给你序列化、Display 和解析功能。
步骤 2:存储层(第 9、7 章)
#![allow(unused)]
fn main() {
// src/storage.rs
use std::fs;
use std::path::PathBuf;
use crate::task::Task;
/// 获取任务文件的路径 (~/.rustdo.json)
fn task_file_path() -> PathBuf {
let home = dirs::home_dir().expect("Could not determine home directory");
home.join(".rustdo.json")
}
/// 从磁盘加载任务 —— 如果文件不存在则返回空的 Vec
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)?; // ? 传播 io::Error
let tasks: Vec<Task> = serde_json::from_str(&content)?; // ? 传播 serde 错误
Ok(tasks)
}
/// 保存任务到磁盘
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 对比:Python 使用
Path.read_text()+json.loads()。Rust 使用fs::read_to_string()+serde_json::from_str()。注意?—— 每个错误都是显式传播的。
步骤 3:命令 Enum(第 6 章)
#![allow(unused)]
fn main() {
// src/command.rs
use crate::task::Priority;
/// 所有可能的命令 —— 每个动作一个 enum 变体
pub enum Command {
Add { title: String, priority: Priority },
List { show_done: bool },
Done { id: u32 },
Remove { id: u32 },
Stats,
Help,
}
impl Command {
/// 解析命令行参数为 Command
/// (在生产环境中,你会使用 `clap` —— 这是教育性质的)
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 对比:Python 使用
argparse或click。这个手写的解析器展示了如何在 enum 上使用match来替代 Python 的 if/elif 链。对于真实项目,使用clapcrate。
步骤 4:业务逻辑(第 5、12、7 章)
#![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) // 迭代器 + 闭包(第 12 章)
.collect();
if filtered.is_empty() {
println!("No tasks! 🎉");
return Ok(());
}
for task in &filtered {
println!(" {task}"); // 使用 Display trait(第 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(第 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(第 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;
// 使用迭代器按优先级分组(第 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(())
}
}
使用的关键 Rust 模式:
iter().map().max()、iter().filter().collect()、iter_mut().find()、retain()、iter().filter().count()。这些替代了 Python 的列表推导式、next(x for x in ...)和Counter模式。
步骤 5:将它们组合在一起(第 8 章)
// 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 入口)"] --> CMD["command.rs<br/>(解析参数)"]
CMD --> ACT["actions.rs<br/>(业务逻辑)"]
ACT --> STORE["storage.rs<br/>(JSON 持久化)"]
ACT --> TASK["task.rs<br/>(数据模型)"]
STORE --> TASK
style CLI fill:#d4edda
style CMD fill:#fff3cd
style ACT fill:#fff3cd
style STORE fill:#ffeeba
style TASK fill:#ffeeba
步骤 6: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 等价:这相当于
pyproject.toml的[project.dependencies]。cargo add serde serde_json chrono dirs类似于pip install。
步骤 7:测试(第 14 章)
#![allow(unused)]
fn main() {
// src/task.rs —— 添加在底部
#[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("⬜")); // 还未完成
}
#[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 等价:
pytest测试。使用cargo test而不是pytest。不需要测试发现机制 ——#[test]显式标记测试函数。
扩展目标
基本版本完成后,尝试这些增强:
-
添加
clap进行参数解析 —— 用clap的 derive 宏替换手写解析器:#![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, } } -
添加彩色输出 —— 使用
coloredcrate 实现终端颜色(类似 Python 的colorama)。 -
添加截止日期 —— 添加
Option<NaiveDate>字段并过滤逾期任务。 -
添加标签/分类 —— 使用
Vec<String>存储标签并使用.iter().any()过滤。 -
做成库 + 二进制 —— 拆分为
lib.rs+main.rs以便逻辑可重用(第 8 章模块模式)。
你练习了什么
| 章节 | 概念 | 出现位置 |
|---|---|---|
| 第 3 章 | 类型和变量 | Task struct 字段、u32、String、bool |
| 第 5 章 | 集合 | Vec<Task>、retain()、push() |
| 第 6 章 | Enum + 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 章 | From/Into | FromStr 用于 Priority、.into() 用于错误转换 |
| 第 12 章 | 迭代器 | filter、map、find、count、collect |
| 第 14 章 | 测试 | #[test]、#[cfg(test)]、断言宏 |
🎓 恭喜! 如果你构建了这个项目,你已经使用了本书涵盖的所有主要 Rust 概念。你不再是一个学习 Rust 的 Python 开发者 —— 你是一个懂 Rust 的开发者,同时也知道 Python。