前言
Rust 编译器帮助我们避免了许多常见的内存安全和并发错误,但依然存在一些“坏味道”。这些坏味道不会直接导致编译错误,却可能意味着代码在可维护性、性能或抽象设计上存在问题。本文参考《重构》的风格,整理了常见的 Rust 坏味道及改进方式。
内容
1. 到处 .clone()
坏味道示例:
1 | fn main() { |
改进方式:
1 | fn main() { |
要点: 优先借用 &T
,仅在必要时使用 clone
。过度拷贝会导致性能问题。
2. 随手 .unwrap()
坏味道示例:
1 | let v = s.parse::<i32>().unwrap(); |
改进方式:
1 | let v: i32 = s.parse()?; |
要点: 使用 ?
传播错误,返回 Result
,避免随时 panic。
3. API 参数总用 String
坏味道示例:
1 | fn greet(name: String) { |
改进方式:
1 | fn greet(name: &str) { |
要点: 参数尽量使用 &str
,调用方无需额外分配内存。
4. 错误类型用 String
坏味道示例:
1 | fn parse_age(s: &str) -> Result<u32, String> { |
改进方式:
1 |
|
要点: 定义枚举错误类型,或使用 thiserror
/anyhow
。
5. 滥用 Rc<RefCell<T>>
坏味道示例:
1 | use std::rc::Rc; |
改进方式:
1 | struct Counter { |
要点: 优先使用借用和 &mut
,必要时再考虑智能指针。
6. 深层 match
嵌套
坏味道示例:
1 | match option { |
改进方式:
1 | if let Some(Ok(x)) = option { |
或者使用 map
, and_then
。
要点: 利用模式匹配简化控制流。
7. 到处 mut
坏味道示例:
1 | let mut x = 0; |
改进方式:
1 | let x: i32 = (0..10).sum(); |
要点: 优先函数式写法,减少可变状态。
8. 无意义的 trait 实现
坏味道示例:
1 | impl Default for Config { |
改进方式: 仅在 Default
有合理含义时实现。
要点: 避免“为了编译过”而写无语义代码。
9. 模块全 pub
坏味道示例:
1 | pub struct Foo { |
改进方式:
1 | pub struct Foo { |
要点: 隐藏实现细节,提供受控接口。
10. 在热路径频繁分配
坏味道示例:
1 | for _ in 0..1000 { |
改进方式:
1 | let mut v = Vec::with_capacity(1000); |
要点: 预分配或复用 buffer。
11. 随意 unsafe
坏味道示例:
1 | unsafe { *ptr = 5; } |
改进方式:
- 封装在安全 API 内
- 写明 invariants(不变量)
要点: unsafe
应该是小心隔离的边角代码,不是随处可见的习惯。
12. 缺乏测试与 bench
坏味道示例: 项目中无单元测试。
改进方式: 使用 Rust 内置测试框架:
1 |
|
要点: 测试和 bench 是防止坏味道扩散的护栏。
总结
Rust 的坏味道主要集中在 所有权处理、错误管理、模块设计 上。如果代码中到处充斥 .clone()
、.unwrap()
、Rc<RefCell>
、Result<T, String>
,或 pub struct
全开放,那就是需要重构的信号。本手册希望成为 Rust 程序员日常自检与改进的参考。