实现层面:所有权、错误处理与代码细节
提炼自指南的 Ownership and borrowing / Implementation concerns / Use the right per-codebase test framework 章节。
所有权与借用
避免 Rc / Arc
优先唯一所有权与借用。Rust 通常能不靠共享所有权指针表达对象图;这些指针有运行时开销和自身复杂性,从其他语言来的工程师容易过快使用。
例外:替代的所有权与借用结构复杂到难以编写、理解或维护(review 时判断)。
借用优先于深拷贝
.clone() 只在真正需要深拷贝时用,不当借用的替代品;让代码顺应 Rust 的所有权与借用规则来设计。借用关系过分复杂时,clone 是务实选择;选择时同时考虑性能需求。
注:clone Rc/Arc 指针不是深拷贝,是其正常用法。
不要过度依赖类型推断
写到"熟悉代码库的人能推断出类型"的程度。类型加在变量声明上,而不是加 turbofish;若编译器逼着写 turbofish,用 _ 占位通常可以。
应加显式类型的场景:调用泛型返回类型的方法(.into()、.parse());复杂迭代器链的结果。
模式匹配优于 check-then-unwrap
输入数据保证不会 panic 时,用模式匹配向编译器证明正确性:
// 好:match 向编译器证明了 x 是 Some,显然不会 panic
if let Some(value) = option {
use_value(value);
}
// 不好:unwrap 的正确性依赖运行时检查
if option.is_some() {
use_value(option.unwrap());
}
expect 优于 unwrap
类型系统表达不了的不变量必须 panic 时,用 expect 记录理由。
匹配 #[non_exhaustive] enum 时列出模式
尽量在 match 中列出全部变体;不关心的变体归入通配臂(Shape::Triangle | _ => {});只关心特定变体时用 if let 或 let … else。例外:只关心特定变体但 match 比 if let 更干净时。
用上下文字符串说清断言
assert! 的可选格式串用于说明断言为何应当成立、或为何被违反。特别值得加上下文的场景:本想加注释时(格式串的好处是会进日志);失败信息会到用户手里时;记录某个值有助于理解失败时;区分"调用方误用 API"与"实现有 bug"时。
// 解释断言为何成立
assert!(
values.is_sorted(),
"`values` is populated from a `PriorityQueue`, so we expect it to be sorted"
);
// 记录值
assert!(refund > 0, "Refund must be positive, got {refund:?}");
// 标记 API 误用
assert!(self.stream.is_some(), "Call connect() before executing queries");
// 不好:只复述条件
assert!(values.is_sorted(), "values should always be sorted");
迭代器组合器链 vs 直接迭代
- 避免长而复杂的组合器链(尤其含条件、改外部状态的)。
- 不要在
for头部构造链:先赋给具名变量再迭代——减少杂乱,变量名还能说明链的用途。 - 避免使用
Iterator::for_each(确有理由除外),for循环通常更好读。 - 短链、组合器本身承载大量逻辑时组合器更优:
values.iter().any(|&v| v > threshold)好于手写 flag + break。
// 不好:for 头部构造链
for bug in bugs.iter().filter_map(|d| parse(d).ok()).filter(|b| b.priority <= Priority::P1) { … }
// 好:具名变量
let urgent_bugs = bugs
.iter()
.filter_map(|d| parse(d).ok())
.filter(|b| b.priority <= Priority::P1);
for bug in urgent_bugs { … }
// 不好:链里夹条件与外部状态
let _processed: Vec<_> = items
.into_iter()
.map(transform_that_may_fail)
.map(|result| { if result.is_err() { error_count += 1; } result })
.filter_map(Result::ok)
.collect();
// 好:对应的 for 循环
let mut processed = Vec::with_capacity(items.len());
for item in items {
match transform_that_may_fail(item) {
Ok(val) => processed.push(val),
Err(_) => error_count += 1,
}
}
禁用 panic::catch_unwind
第一方 Rust 代码禁用。例外:仅作纵深防御且绝不依赖其任何性质(代码库须已尽力避免 panic),且仅在可能以 panic=unwind 运行的环境中适用(Chrome、Android、Fuchsia 均要求 panic=abort,不适用)。
拼接与替换用 format! / write!
- 拼接超过两段字符串:
format!("{a}{b}{c}")而非a + b + c。 - 对字面量做替换:用格式串而非
str::replace。
理由:比 + 拼接分配更少;标准格式串优于自造模板语法。
测试框架
多数 Google 代码库用各自测试框架而非 #[test]:Chrome 用 rust_gtest_interop(Rust 测试并入 C++ 测试二进制);Fuchsia 用 #[fuchsia::test]。
(注:通用 Rust 项目照常 #[test] + cargo test 即可——此句为使用本技能时的补充说明,非指南原文。)