Rust 编码
工作流
-
了解全局。 开始编写代码前,先阅读项目的权威文件:
rust-toolchain.toml、Cargo.toml(edition、rust-version、[lints]、[workspace])、clippy.toml、rustfmt.toml、.cargo/config.toml,以及 CI 配置、justfile、Makefile或xtask中的规范检查命令。阅读你要修改的模块及其相邻模块;重用它们的辅助函数、错误类型和模式。 完成标志: 你能说出 edition、MSRV、lint 策略、每一个disallowed-methods/disallowed-types条目,以及项目的检查命令。 -
设计类型。 遵循类型章节,先设计数据再设计控制流。如果修改库的公开 API,请阅读
references/api.md。 完成标志: 变更所依赖的每个不变量都已编码到类型中,或者由一个注明其名称的expect维护。 -
在紧凑循环中实现。 小步前进,每步后运行
cargo check。阅读完整的诊断信息:它的help:行通常指出了修复方法,rustc --explain E0502会展开错误码。对于依赖项的 API,在其锁定版本(Cargo.lock)下的$CARGO_HOME/registry/src/目录中阅读源代码。批量重命名通过 rust-analyzer 重命名或ast-grep进行;cargo clippy --fix可应用机器建议的修复。Bug 修复: 先编写回归测试并看到它变红。 完成标志: 变更编译通过且你修改的行没有警告,并且任何回归测试在修复前为红色,修复后为绿色。 -
验证。 运行步骤 1 中的项目命令;否则:
cargo fmt --all cargo clippy --workspace --all-targets --all-features -- -D warnings cargo test --workspace --all-features- 没有
[lints]策略的项目:另外运行cargo clippy --all-targets -- -W clippy::pedantic并修复你修改的行上的提示。 - 互斥特性:将
--all-features替换为 CI 测试的每个特性集。 cargo nextest会跳过文档测试:添加cargo test --doc。- 公共文档更改:
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features。 - 触及了
unsafe:运行references/unsafe.md。 - 从根本上修复 lint。当有必要抑制时,将
#[expect(clippy::lint_name, reason = "...")]限定在最小的代码项上。 完成标志: 每条命令都以 0 退出,你修改的行不引发任何警告,并且每个新的抑制都带有reason。
- 没有
-
自检差异。 重新阅读完整的
git diff(而不是你的记忆),对照规则以及差异所触及的每个引用文件的规则进行检查。 完成标志: 每个块都是任务必需的,原型代码和dbg!/todo!已清理,文档注释与行为一致,并且每条适用规则对每个块都成立。
审查他人代码:运行步骤 1,尽可能运行步骤 4 的命令,然后对其差异应用步骤 5。以 path:line、规则和修复方式报告每个发现。
规则
所有权
- 参数中使用借用:
&str、&[T]、&Path、&T。仅当函数需要存储或消费值时,才按值获取String、Vec<T>或T。 - 每个
.clone()都应有其合理性:调用方需要所有权,或者该值是一个廉价的句柄(Arc::clone(&handle))。 - 当借用检查器报错时,首先重构:缩短借用、分别借用不相交的字段、使用
std::mem::take/replace、使用索引访问Vec。共享可变所有权(Rc<RefCell<_>>、Arc<Mutex<_>>)是最后的手段。 - 当调用方仅需迭代时,返回
impl Iterator<Item = T>。
类型
- 使非法状态不可表示:使用带有每个状态数据的
enum,而不是bool标志加上Option字段;为 ID、单位和经过验证的值使用 newtype(struct Port(NonZeroU16))。 - 解析,而非验证:在边界处一次性转换不可信输入(
TryFrom、FromStr、#[serde(try_from = "Raw")]),并向内传递已解析的类型。 - 对所有属于你的枚举进行穷举匹配。仅当未来的变体也应该走那个分支时,才使用
_分支。 - 使用
From/TryFrom转换整数;as会静默截断和改变符号位。
错误与恐慌
- 库:每个错误域使用一个
thiserror枚举。二进制程序:使用anyhow::Result并配合.context(...)。 - 恐慌意味着 bug。
unwrap()只用于测试;生产代码使用?,或使用expect("...")说明使失败变得不可能的不可变条件。
当前惯用法
较旧的模式在训练数据中占主导地位。当 rust-version 允许时,优先使用当前形式;新 crate 使用 edition = "2024"。clippy::incompatible_msrv 只能捕获较新的标准库 API;语言和 Cargo 特性需要使用 cargo +<msrv> check。
| 优先使用 | 而不是 | 自版本 |
|---|---|---|
std::sync::LazyLock、OnceLock | lazy_static!、once_cell | 1.80、1.70 |
let Some(x) = opt else { return ... }; | match + 提前返回 | 1.65 |
if let Some(x) = a && x > 0 | 嵌套 if let | 1.88,edition 2024 |
trait 中的原生 async fn | #[async_trait],对于 dyn Trait 仍然需要 | 1.75 |
| `async | {} 闭包、AsyncFn` 约束 | |
#[expect(lint, reason = "...")] | #[allow(lint)] | 1.81 |
&raw const x、&raw mut x | ptr::addr_of! | 1.82 |
opt.is_some_and(f)、opt.is_none_or(f) | map_or(false, f)、map_or(true, f) | 1.70、1.82 |
std::hint::black_box | criterion::black_box | 1.66 |
Cargo.toml 中的 [lints] 用于共享策略 | 每个 crate 根中的 #![warn(...)] | 1.74 |
cargo:: 构建脚本指令 | cargo: | 1.77 |
文档与注释
- 公开项使用
///文档:可能出错的函数写# Errors,可能恐慌的函数写# Panics,示例作为文档测试使用?。 - 注释解释原因;代码展示内容。
依赖
- 重用工作空间已有的依赖(
[workspace.dependencies]、cargo tree -i <crate>),在标准库可以满足需求时优先使用标准库。 - 使用
cargo add添加依赖(考虑 MSRV),仅启用你需要的特性。
引用文件
加载与你当前所在分支匹配的文件:
references/api.md:设计或修改库的公开 API、命名、trait 实现、语义版本控制。references/errors.md:定义错误类型、传播、恐慌 vsResult。references/async.md:异步、tokio、线程、通道、锁、原子操作。references/unsafe.md:unsafe、FFI、原始指针、手动Send/Sync。references/cargo.md:Cargo.toml、工作空间、特性、配置文件、构建脚本、crate 选择。references/lints.md:为 crate 或工作空间设置 lint 配置。references/testing.md:编写测试、属性测试、快照测试、基准测试。references/perf.md:优化已测量的热点路径。