Skip to content

最佳实践

学习目标: 养成十大习惯、常见陷阱与修复、结构化 3 个月学习路径、 完整的 Python→Rust” Rosetta Stone”参考表,以及推荐学习资源。

难度: 🟡 中级

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
  1. 使用 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. 自由派生 trait — #[derive(Debug, Clone, PartialEq)] 应该在大多数结构体上。这是免费的,让测试更容易。

  7. 虔诚地使用 cargo clippy — 它捕获数百种样式和正确性问题。把它当作 Rust 的 ruff。

  8. 不要与借用检查器对抗 — 如果你在对抗它,你可能数据结构搞错了。重构使所有权清晰。

  9. 使用枚举表示状态机 — 使用枚举而非字符串标志或布尔值。编译器确保你处理每种状态。

  10. 先克隆,后优化 — 学习时,自由使用 .clone() 来避免所有权复杂性。只在性能分析显示需要时才优化。

错误原因修复
到处 .unwrap()运行时 panic使用 ? 或 match
String 而非 &str不必要的分配参数使用 &str
for i in 0..vec.len()不够惯用for item in &vec
忽略 clippy 警告错过简单改进cargo clippy
太多 .clone() 调用性能开销重构所有权
巨大的 main() 函数难以测试提取到 lib.rs
不使用 #[derive()]重复造轮子派生常用 trait
对错误 panic不可恢复返回 Result<T, E>

操作 Python 3.12 Rust (发布版) 加速比
───────────────────── ──────────── ────────────── ─────────
Fibonacci(40) 约 25s 约 0.3s 约 80x
排序 1000 万整数 约 5.2s 约 0.6s 约 9x
解析 100MB JSON 约 8.5s 约 0.4s 约 21x
正则匹配 100 万次 约 3.1s 约 0.3s 约 10x
HTTP 服务器 (req/s) 约 5000 约 150,000 约 30x
SHA-256 1GB 文件 约 12s 约 1.2s 约 10x
解析 100 万行 CSV 约 4.5s 约 0.2s 约 22x
字符串拼接 约 2.1s 约 0.05s 约 42x

注意:带 C 扩展的 Python(NumPy 等)会显著缩小数值工作的差距。这些基准测试比较纯 Python vs 纯 Rust。

Python: Rust:
───────── ─────
- 每个对象头:28 字节/对象 - 无对象头
- int:28 字节(即使是 0) - i32:4 字节,i64:8 字节
- str "hello":54 字节 - &str "hello":16 字节(ptr + len)
- 1000 个 int 的 list:约 36 KB - Vec<i32>:约 4 KB
(8 KB 指针 + 28 KB int 对象)
- 100 项 dict:约 5.5 KB - 100 项 HashMap:约 2.4 KB
典型应用总内存:
- Python:50-200 MB 基线 - Rust:1-5 MB 基线

陷阱 1:“借用检查器不让我过”

Section titled “陷阱 1:“借用检查器不让我过””
// 问题:尝试在迭代时修改
let mut items = vec![1, 2, 3, 4, 5];
// for item in &items {
// if *item > 3 { items.push(*item * 2); } // ❌ 借用 mut 时不能借用
// }
// 解决方案 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);
// 有疑问时:
// - &str 用于函数参数
// - String 用于结构体字段和返回值
// - &str 字面量("hello")可以在任何需要 &str 的地方使用
fn process(input: &str) -> String { // 接受 &str,返回 String
format!("Processed: {}", input)
}

陷阱 3:“我想念 Python 的简洁”

Section titled “陷阱 3:“我想念 Python 的简洁””
// 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())
// 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"]
}

  • 安装 Rust,设置 VS Code + rust-analyzer
  • 完成本指南第 1-4 章(类型、控制流)
  • 编写 5 个小程序将 Python 脚本转换为 Rust
  • 熟悉 cargo build、cargo test、cargo clippy
  • 完成第 5-8 章(结构体、枚举、所有权、模块)
  • 将 Python 数据处理脚本重写为 Rust
  • 练习 Option<T> 和 Result<T, E> 直到自然
  • 仔细阅读编译器错误消息 — 它们在教你
  • 完成第 9-12 章(错误处理、trait、迭代器)
  • 用 clap 和 serde 构建 CLI 工具
  • 为 Python 项目的热点编写 PyO3 扩展
  • 练习迭代器链直到感觉像推导式
  • 完成第 13-16 章(并发、unsafe、测试)
  • 用 axum 和 tokio 构建 Web 服务
  • 为开源 Rust 项目做贡献
  • 阅读《Programming Rust》(O’Reilly) 深入理解
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__ 方法Trait(Display、Add 等)10
lambda|args| body12
generator yieldimpl Iterator12
列表推导式.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(上下文管理器)基于作用域的 Drop15
json.dumps/loadsserde_json15

你会想念的 Python 特性:
- REPL 和交互式探索
- 快速原型开发速度
- 丰富的 ML/AI 生态系统(PyTorch 等)
- "能用"的动态类型
- 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!("未找到"),
}
}

关键收获:在 Rust 中有害的 Python 习惯:克隆一切(使用借用)、使用哨兵值如 ""(使用 Option)、当借用足够时获取所有权、对索引使用有符号整数。


Rust for Python 程序员培训指南 完