Skip to content
Clone
main
123 lines 4.8 KB Markdown

实现层面:所有权、错误处理与代码细节

提炼自指南的 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 即可——此句为使用本技能时的补充说明,非指南原文。)