Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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–12Trait、泛型、闭包、迭代器1–2 天你能将列表推导式转换为迭代器链
13并发1 天你能用 Arc<Mutex<T>> 编写线程安全计数器
14Unsafe、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. 入门指南 🟢

3. 内置类型与变量 🟢

4. 控制流 🟢

5. 数据结构与集合 🟢

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 🟡

12. 闭包与迭代器 🟡

  • 闭包 vs Lambda 表达式
  • [迭代器 vs 生成器](ch12-closures-and-iterators.md#迭代器 vs 生成器)
  • [宏:编写代码的代码](ch12-closures-and-iterators.md#为什么-rust 需要宏)

第三部分 — 高级主题与迁移

13. 并发编程 🔴

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. 最佳实践 🟡


第四部分 — 实战项目

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 开发者重要:

  1. 互补技能:Rust 和 Python 解决不同的问题
  2. PyO3 桥梁:编写可从 Python 调用的 Rust 扩展
  3. 性能理解:了解 Python 为何慢以及如何修复热点
  4. 职业发展:系统编程专业知识越来越有价值
  5. 云成本: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

概念PythonRust关键差异
类型系统动态(鸭子类型)静态(编译时)错误在编译前捕获
内存垃圾回收(引用计数 + 循环 GC)所有权系统零成本、确定性清理
None/null任何地方的 NoneOption<T>编译时 None 安全
错误处理raise/try/exceptResult<T, E>显式、无隐藏控制流
可变性全部可变默认不可变需要显式选择可变
速度解释型(慢 10-100 倍)编译型(C/C++ 速度)快几个数量级
并发GIL 限制线程无 GIL,Send/Sync trait默认真正并行
依赖管理pip install / poetry addcargo add内置依赖管理
构建系统setuptools/poetry/hatchCargo单一统一工具
打包配置pyproject.tomlCargo.toml类似的声明式配置
REPLpython 交互式无 REPL(用测试/cargo run)编译优先工作流
类型提示可选、不强制必需、编译器强制类型不是装饰品

练习

🏋️ 练习:思维模型检查(点击展开)

挑战:对于每个 Python 代码片段,预测 Rust 会有什么不同要求。无需编写代码 — 只需描述约束。

  1. x = [1, 2, 3]; y = x; x.append(4) — Rust 中会发生什么?
  2. data = None; print(data.upper()) — Rust 如何防止这个错误?
  3. import threading; shared = []; threading.Thread(target=shared.append, args=(1,)).start() — Rust 要求什么?
🔑 解答
  1. 所有权移动:let y = x; 会移动 x — x.push(4) 是编译错误。你需要 let y = x.clone(); 或者用 let y = &x; 借用。
  2. 无 null:data 不能是 None,除非它是 Option<String>。你必须 match 或使用 .unwrap() / if let — 不会有意外的 NoneType 错误。
  3. 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 工具

用途PythonRust
语言运行时python(解释器)rustc(编译器,很少直接调用)
包管理器pip / poetry / uvcargo(内置)
项目配置pyproject.tomlCargo.toml
锁定文件poetry.lock / requirements.txtCargo.lock
虚拟环境venv / conda不需要(依赖按项目管理)
格式化器black / ruff formatrustfmt(内置:cargo fmt)
Linterruff / flake8 / pylintclippy(内置:cargo clippy)
类型检查mypy / pyright编译器内置(始终开启)
测试运行器pytestcargo test(内置)
文档工具sphinx / mkdocscargo doc(内置)
REPLpython / 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 项目并编写一个程序:

  1. 声明一个变量 name 包含你的名字(类型 &str)
  2. 声明一个可变变量 count 从 0 开始
  3. 使用 for 循环从 1..=5 递增 count 并打印 "Hello, {name}! (count: {count})"
  4. 循环结束后,用 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

数值类型

PythonRust说明
int(任意精度)i8, i16, i32, i64, i128, isizeRust 整数有固定大小
int(无符号:无独立类型)u8, u16, u32, u64, u128, usize显式的无符号类型
float(64 位 IEEE 754)f32, f64Python 只有 64 位浮点数
boolbool相同概念
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:?}");                     // 相同,内联语法
}

快速参考

PythonRust说明
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 字节的头部 + 独立的堆分配浮点对象。Rust Rectangle 正好是栈上的 16 字节 — 无间接、无 GC 压力。

📌 另见:第 10 章 — 特征与泛型 涵盖为你的结构体实现 Display、Debug 等特征以及运算符重载。

关键映射:Python 魔术方法 → Rust 特征

PythonRust用途
__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
}

快速参考

PythonRust说明
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 lstvec.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;
}
}

快速参考

PythonRust说明
d[key] = vald.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 dd.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

其他集合

PythonRust说明
set()HashSet<T>use std::collections::HashSet;
collections.dequeVecDeque<T>use std::collections::VecDeque;
heapqBinaryHeap<T>默认最大堆
collections.OrderedDictIndexMap(crate)HashMap 不保证顺序
sortedcontainers.SortedListBTreeSet<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 等价物

模式PythonRust
检查是否存在if x is not None:if let Some(x) = opt { ... }
默认值x or defaultopt.unwrap_or(default)
默认工厂x or compute()opt.unwrap_or_else(|| compute())
存在时转换f(x) if x else Noneopt.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 raiseopt.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}!")
}

修复的错误:

  1. 不可变借用 + 可变:first 借用 names,然后 push 修改它。修复:在 push 之前使用 first。
  2. 从 Vec 移动:names[0] 尝试从 Vec 移动 String(不允许)。修复:用 &names[0] 借用。
  3. 函数获取所有权:make_greeting(String) 消耗值。修复:改为接受 &str。

Crate 与模块

Rust 模块 vs Python 包

你将学到: mod 和 use 与 Python import 的对比、可见性(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。

关键差异

概念PythonRust
模块 = 文件✅ 自动必须用 mod 声明
包 = 目录__init__.pymod.rs + mod 声明
默认公开✅ 一切❌ 默认私有
设为公开_前缀 约定pub 关键字
导入语法from x import yuse x::y;
通配符导入from x import *use x::*;(不鼓励)
相对导入from . import siblinguse 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用途
requestsreqwestHTTP 客户端
json(标准库)serde_jsonJSON 解析
pydanticserde序列化/验证
pathlibstd::path(标准库)路径处理
os / shutilstd::fs(标准库)文件操作
reregex正则表达式
loggingtracing / log日志
click / argparseclapCLI 参数解析
asynciotokio异步运行时
datetimechrono日期时间
pytest内置 + rstest测试
dataclasses#[derive(...)]数据结构
typing.ProtocolTraits结构类型
subprocessstd::process(标准库)运行外部命令
sqlite3rusqliteSQLite
sqlalchemydiesel / sqlxORM / SQL 工具包
fastapiaxum / actix-webWeb 框架

工作区 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)
}
}

错误处理快速参考

PythonRust说明
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>,要求:

  1. 拒绝空字符串,错误为 "empty input"
  2. 解析字符串为 u16,将解析错误映射为 "invalid number: {original_error}"
  3. 拒绝小于 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 ProtocolRust 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);
}
}

泛型快速参考

PythonRust说明
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 TraitPython 等价用途
Display__str__人类可读字符串
Debug__repr__调试字符串(可推导)
Clonecopy.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 和 Into trait 用于零成本类型转换、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&lt;A&gt; for B"] -->|"自动生成"| B["impl Into&lt;B&gt; 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
}

转换快速参考

PythonRust说明
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 的自定义错误类型。


练习

🏋️ 练习:温度转换库(点击展开)

挑战:构建一个小型温度转换库:

  1. 定义 Celsius(f64)、Fahrenheit(f64) 和 Kelvin(f64) 结构体
  2. 实现 From<Celsius> for Fahrenheit 和 From<Celsius> for Kelvin
  3. 实现 TryFrom<f64> for Kelvin,拒绝低于绝对零度的值(-273.15°C = 0K)
  4. 为所有三种类型实现 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&lt;i32&gt;<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();
}

常见迭代器方法

PythonRust说明
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/Sync trait 用于编译时线程安全、 Arc<Mutex<T>> 与 Python threading.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 在编译时捕获它们。这就是"无畏并发"。
}

并发原语对比

PythonRust用途
threading.Lock()Mutex<T>互斥锁
threading.RLock()Mutex<T>(不可重入)重入锁(使用方式不同)
threading.RWLock(无)RwLock<T>多读或一写
threading.Event()Condvar条件变量
queue.Queue()mpsc::channel()线程安全 channel
multiprocessing.Poolrayon::ThreadPool线程池
concurrent.futuresrayon / 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 asyncioRust 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/Sync trait,它们是编译器用于检查线程安全的自动 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:

  1. 永远不要让 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 代码
        }
    }
    }
  2. 共享结构体使用 #[repr(C)] —— 如果 Python/C 代码直接读取结构体字段,你必须使用 #[repr(C)] 保证 C 兼容布局。如果你传递不透明指针(PyO3 为 #[pyclass] 做的),则不需要。

  3. 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     # 顺序(用于有副作用的测试)

测试快速参考表

pytestRust说明
assert x == yassert_eq!(x, y)相等
assert x != yassert_ne!(x, y)不等
assert conditionassert!(condition)布尔
assert condition, "msg"assert!(condition, "msg")带消息的断言
pytest.raises(E)#[should_panic]期望 panic 发生
@pytest.fixture在测试或辅助函数中设置Rust 无内置 fixture
@pytest.mark.parametrizerstest crate参数化测试
conftest.pytests/common/mod.rs共享测试辅助
pytest.skip()#[ignore]跳过测试
tmp_path fixturetempfile 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 清单

数据处理与序列化

任务PythonRust Crate说明
JSONjsonserde_json类型安全的序列化
CSVcsv, pandascsv流式处理,低内存
YAMLpyyamlserde_yaml配置文件
TOMLtomllibtoml配置文件
数据验证pydanticserde + 自定义逻辑编译时验证
日期/时间datetimechrono完整的时区支持
正则reregex非常快
UUIDuuiduuid相同的 API

Web 与网络

任务PythonRust Crate说明
HTTP 客户端requestsreqwestAsync 优先
Web 框架FastAPI/Flaskaxum / actix-web非常快
WebSocketwebsocketstokio-tungsteniteAsync
gRPCgrpciotonic完整支持
数据库 (SQL)sqlalchemysqlx / diesel编译时检查 SQL
Redisredis-pyredisAsync 支持

CLI 与系统

任务PythonRust Crate说明
CLI 参数argparse/clickclapDerive 宏
彩色输出coloramacolored终端彩色输出
进度条tqdmindicatif类似的用户体验
文件监控watchdognotify跨平台支持
日志loggingtracing结构化,async 就绪
环境变量os.environstd::env + dotenvy支持 .env 文件
子进程subprocessstd::process::Command内置
临时文件tempfiletempfile相同的 crate 名称!

测试

任务PythonRust Crate说明
测试框架pytest内置 + rstestcargo test
Mockingunittest.mockmockall基于 trait
属性测试hypothesisproptest相似的 API
快照测试syrupyinsta快照审批
基准测试pytest-benchmarkcriterion统计学
代码覆盖率coverage.pycargo-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/4747/47(不变)

核心教训:你不需要重写整个应用。找到占 95% 时间的 5% 代码,用 PyO3 将其重写为 Rust,其他部分保持 Python 不变。团队从“我们需要添加更多服务器“变成了“一台服务器就够了“。


练习

🏋️ 练习:迁移决策矩阵(点击展开)

挑战:你有一个 Python Web 应用,包含以下组件。对于每个组件,决定:保留 Python、用 Rust 重写或使用 PyO3 桥接。说明每个选择的理由。

  1. Flask 路由处理器(请求解析、JSON 响应)
  2. 图像缩略图生成(CPU 绑定,每天处理 10k 张图像)
  3. 数据库 ORM 查询(SQLAlchemy)
  4. 2GB 金融文件的 CSV 解析器(每夜运行)
  5. 管理后台仪表板(Jinja2 模板)
🔑 解答
组件决策理由
Flask 路由处理器🐍 保留 PythonI/O 绑定、框架依赖重、Rust 收益低
图像缩略图生成🦀 PyO3 桥接CPU 绑定热点,保留 Python API,Rust 内部实现
数据库 ORM 查询🐍 保留 PythonSQLAlchemy 成熟,查询是 I/O 绑定
CSV 解析器(2GB)🦀 PyO3 桥接或完全 RustCPU + 内存绑定,Rust 的零拷贝解析优势明显
管理后台仪表板🐍 保留 PythonUI/模板代码,无性能问题

核心要点:迁移的最佳目标是具有清晰边界的 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 大习惯

  1. 在 enum 上使用 match 而不是 if isinstance()

    # Python                              # Rust
    if isinstance(shape, Circle): ...     match shape { Shape::Circle(r) => ... }
    
  2. 让编译器指导你 —— 仔细阅读错误信息。Rust 的编译器是所有语言中最好的。 它会告诉你哪里错了以及如何修复。

  3. 函数参数优先使用 &str 而不是 String —— 接受最通用的类型。 &str 可以兼容 String 和字符串字面量。

  4. 使用迭代器而不是索引循环 —— 迭代器链比 for i in 0..vec.len() 更地道, 通常性能也更好。

  5. 拥抱 Option 和 Result —— 不要对所有东西都用 .unwrap()。 使用 ?、map、and_then、unwrap_or_else。

  6. 自由地使用 derive —— #[derive(Debug, Clone, PartialEq)] 应该出现在 大多数结构体上。这是零成本的,让测试更简单。

  7. 严格使用 cargo clippy —— 它捕获数百个风格和正确性问题。 把它当作 Rust 版的 ruff。

  8. 不要与 borrow checker 对抗 —— 如果你在与它对抗,可能是你结构化数据的方式不对。 重构代码让所有权更清晰。

  9. 使用 enum 表示状态机 —— 不要使用字符串标志或布尔值,使用 enum。 编译器确保你处理了每种状态。

  10. 先 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 罗塞塔石碑

PythonRust章节
listVec<T>5
dictHashMap<K,V>5
setHashSet<T>5
tuple(T1, T2, ...)5
classstruct + impl5
@dataclass#[derive(...)]5, 12a
Enumenum6
NoneOption<T>6
raise/try/exceptResult<T,E> + ?9
Protocol (PEP 544)trait10
TypeVar泛型 <T>10
__dunder__ 方法Traits (Display, Add 等)10
lambda|args| body12
generator yieldimpl Iterator12
list comprehension.map().filter().collect()12
@decorator高阶函数或宏12a, 15
asynciotokio13
threadingstd::thread13
multiprocessingrayon13
unittest.mockmockall14a
pytestcargo test + rstest14a
pip installcargo add8
requirements.txtCargo.lock8
pyproject.tomlCargo.toml8
with (context manager)基于作用域的 Drop15
json.dumps/loadsserde_json15

给 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 链。对于真实项目,使用 clap crate。


步骤 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] 显式标记测试函数。


扩展目标

基本版本完成后,尝试这些增强:

  1. 添加 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,
    }
    }
  2. 添加彩色输出 —— 使用 colored crate 实现终端颜色(类似 Python 的 colorama)。

  3. 添加截止日期 —— 添加 Option<NaiveDate> 字段并过滤逾期任务。

  4. 添加标签/分类 —— 使用 Vec<String> 存储标签并使用 .iter().any() 过滤。

  5. 做成库 + 二进制 —— 拆分为 lib.rs + main.rs 以便逻辑可重用(第 8 章模块模式)。


你练习了什么

章节概念出现位置
第 3 章类型和变量Task struct 字段、u32、String、bool
第 5 章集合Vec<Task>、retain()、push()
第 6 章Enum + matchPriority、Command、穷尽匹配
第 7 章所有权 + 借用&[Task] 与 Vec<Task>、&mut 用于完成
第 8 章模块mod task; mod storage; mod command; mod actions;
第 9 章错误处理Result<T, E>、? 操作符、.ok_or()
第 10 章TraitDisplay、FromStr、Serialize、Deserialize
第 11 章From/IntoFromStr 用于 Priority、.into() 用于错误转换
第 12 章迭代器filter、map、find、count、collect
第 14 章测试#[test]、#[cfg(test)]、断言宏

🎓 恭喜! 如果你构建了这个项目,你已经使用了本书涵盖的所有主要 Rust 概念。你不再是一个学习 Rust 的 Python 开发者 —— 你是一个懂 Rust 的开发者,同时也知道 Python。