mcpField
R
rust-best-practices

rust-best-practices

github.com/existential-birds开发8578 次安装13 周前更新
代码仓库

关于 rust-best-practices

rust-best-practices 是面向 Rust 开发的技能,提供编写惯用、安全且高效代码的指导。它侧重实现过程中的模式与可验证检查,不用于代码审查。

主要功能

  1. 01所有权与编码惯用法
  2. 02Clippy 与 lint 规范
  3. 03基准测试与性能指导
  4. 04泛型、分发与异步 API
  5. 05类型状态与 API 设计
  6. 06文档与错误处理

适用场景

  1. 01选择所有权和指针模式
  2. 02添加 lint 与文档检查
  3. 03验证性能优化主张
  4. 04设计公共及异步 API

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/existential-birds/beagle --skill rust-best-practices

安装后在 Claude Code 中输入/rust-best-practices确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
api-design.md
clippy-config.md
coding-idioms.md
documentation.md
ecosystem-patterns.md
generics-dispatch.md
performance.md
pointer-types.md
type-state-pattern.md

详细介绍

Rust 最佳实践

编写符合惯用法、高性能且安全的 Rust 代码指南。这是一项开发技能,而非审查技能——在构建时使用,而非审查时使用。

快速参考

主题核心规则参考
所有权默认借用,仅在需要独立拥有的副本时克隆references/coding-idioms.md
Clippy每次提交都运行 cargo clippy -- -D warnings;配置工作区 lintreferences/clippy-config.md
性能不要猜测,要测量。先用 --release 进行性能分析。规模增大时留意单态化和缓存行对齐references/performance.md
泛型默认使用静态分发;需要混合类型时使用动态分发references/generics-dispatch.md
类型状态当无效操作应导致编译错误时,在类型系统中编码状态references/type-state-pattern.md
文档用 // 说明原因,用 /// 说明内容和用法,用 //! 说明模块/crate 的用途references/documentation.md
指针根据所有权需求和线程模型选择指针类型references/pointer-types.md
API 设计不令人意外、灵活、清晰、受约束——在类型中编码不变量;留意隐藏契约(重新导出、自动 trait)references/api-design.md
灵活模式Drop guard、扩展 trait、索引指针、crate 前导模块——来自成熟 crate、经久验证的惯用法references/coding-idioms.md
生态系统评估 crate、选择错误处理策略、保持与时俱进references/ecosystem-patterns.md

检查关卡

在宣称需要证据支持的结果之前,先完成简短的步骤序列和通过条件(而不是内心“我检查过了”)。

Clippy 检查通过
  1. 从工作区根目录(或使用 -p <crate>)运行:cargo clippy --all-targets --all-features -- -D warnings。
  2. **通过条件:**退出状态为 0,且命令执行完毕,没有 Clippy-deny 失败。
性能声明
  1. 使用 cargo build --release(或你的基准测试工具)构建,并采用与发布或测量时相同的配置文件。
  2. 使用相同的工具和指标记录前后数据(标明名称),例如 Criterion 的 ns/iter、heaptrack 的分配数据,或磁盘上的火焰图路径。
  3. **通过条件:**能够引用两项测量结果,或者明确说明改动仅涉及正确性或可读性,并未声称性能有所变化。
为已修改的符号编写文档
  1. 对你编辑的 crate 运行 cargo doc --no-deps(在工作区中添加 -p <crate>)。
  2. **通过条件:**文档构建成功;若适用 #![deny(missing_docs)](或 crate 的相关策略),这些符号没有新增缺失文档错误。

编码惯用法

优先使用 &T 而非 .clone(),在参数中使用 &str/&[T],并使用迭代器链而非基于索引的循环。对于 Option/Result,使用 let Ok(x) = expr else { return } 提前返回,并使用 ? 传播错误。对于作用域内的状态变更,使用 drop guard(let _guard = ...,绝不要写 let _ = ...),并配合 mem::replace 或 scopeguard::defer!。通过扩展 trait 为外部类型添加方法(trait MyExt; impl<T: Bound> MyExt for T)。对于图和树结构,优先使用索引指针(slotmap::DefaultKey、代际索引)而非 &T,以绕开生命周期问题,且无需使用 unsafe。精心整理精简的 crate 前导模块,让 glob 导入更方便;添加前导模块属于 semver 次要版本变更(RFC 1105)。所有权、迭代器、导入模式以及这些生态系统层面的惯用法,参见 references/coding-idioms.md。

错误处理

对于可能失败的操作,返回 Result<T, E>。库的错误类型使用 thiserror,二进制程序使用 anyhow。使用 ? 传播错误,测试之外绝不使用 unwrap()。有关 Option/Result 模式,请参阅 references/coding-idioms.md。

Clippy 规范

每次提交都运行 cargo clippy --all-targets --all-features -- -D warnings。在 Cargo.toml 中配置工作区 lint,并将 #[expect(clippy::lint)](而非 #[allow])作为抑制 lint 的标准写法——当抑制变得过时时,它会发出警告。有关 lint 配置和关键 lint,请参阅 references/clippy-config.md。

性能意识

始终使用 --release 进行基准测试,优化前先分析性能,并避免在循环中克隆或过早调用 .collect()。小型类型保留在栈上,仅为递归结构和大型缓冲区分配堆内存。对于规模较大的工作区,注意单态化预算(提取与类型无关的内部函数;在峰值内联并非关键时,将内部泛型改为 dyn)和伪共享(在线程专属的原子变量上使用 #[repr(align(64))] 或 crossbeam::utils::CachePadded;在 Apple Silicon 上使用 align(128))。使用 criterion 进行基准测试——保存基线(--save-baseline main)并在 CI 中进行比较,使用 criterion::black_box(对于指针输入,使用 as_ptr()),并将 I/O 隔离到 iter_batched 的设置阶段。有关性能分析工具、分配建议、单态化模式、缓存行对齐和 criterion 规范,请参阅 references/performance.md。

泛型与分发

默认使用静态分发(impl Trait / <T: Trait>),以实现零成本单态化。仅在异构集合或插件架构中改用 dyn Trait;不需要所有权时,优先使用 &dyn Trait 而非 Box<dyn Trait>。在 edition 2024 中,-> impl Trait 默认捕获作用域内的所有生命周期——使用 + use<'a, T> 精确控制捕获范围。对于静态分发,优先使用 trait 中原生的 async fn,而非 async-trait crate。有关分发取舍、RPIT 捕获规则和异步 trait 指南,请参阅 references/generics-dispatch.md。

类型状态模式

将有效状态编码到类型系统中,使无效操作在编译时出错。适用于具有必填字段的构建器、协议状态机和工作流管道。有关实现模式及应避免使用的情况,请参阅 references/type-state-pattern.md。

文档

使用 // 说明原因,使用 /// 说明公共 API 的用途和用法,使用 //! 说明模块的目的。每个 TODO 都必须关联一个 issue,库 crate 应启用 #![deny(missing_docs)]。使用 #[diagnostic::on_unimplemented] 为公共 trait 提供自定义编译器错误。有关文档测试模式、注释约定和诊断属性,请参阅 references/documentation.md。

API 设计

遵循四项原则:不令人意外(复用标准名称和特性)、灵活(使用泛型和 impl Trait,避免不必要的限制)、清晰(在类型系统中编码不变量,让误用在编译时就报错),以及有所约束(只公开你能长期承诺支持的内容)。对于可能扩展的类型,使用 #[non_exhaustive];对于需要扩展且不能破坏兼容性的 trait,使用密封设计;使用新类型包装外部类型,以控制你的 SemVer 表面。留意隐藏契约——重新导出的外部类型、-> impl Trait 的自动 trait 传播,以及意外的 !Send future——并为公开类型添加编译时 is_normal<T: Sized + Send + Sync + Unpin>() 测试,将这些契约固定下来。新增 trait 时,尽早为 &T/Box<T> 提供 blanket impl(之后再添加会造成破坏性变更)。对于可能失败的清理操作,公开显式的 close()/shutdown() 方法并返回 Result;Drop 不能失败,也不能 .await。有关构建器模式、密封 trait、对象安全机制、Deref 使用规范、可能失败的析构函数以及 SemVer 影响,请参阅 references/api-design.md。

生态系统模式

评估 crate 时,考察近期下载趋势、维护活动、文档质量和传递依赖的体量。库的错误类型使用 thiserror,二进制程序使用 anyhow,需要自定义错误报告器时使用 eyre。如果某个 crate 为小功能引入了大量依赖,不妨优先选择 vendoring 或自行编写代码。运行 cargo-deny 进行许可证和漏洞审计,并运行 cargo-udeps 清理未使用的依赖。有关 crate 评估标准、edition 迁移和必备工具,请参阅 references/ecosystem-patterns.md。

指针类型

根据所有权和线程需求选择指针类型:单一所有者的堆分配使用 Box<T>,共享所有权使用 Rc<T>/Arc<T>,内部可变性使用 Cell/RefCell/Mutex/RwLock。使用 LazyLock/LazyCell(自 1.80 起稳定),替代 lazy_static 或 once_cell。完整的单线程与多线程选择对照表及迁移指南,请参阅 references/pointer-types.md。

析构函数与清理

Drop::drop(&mut self) 不能返回错误,也不能 .await。对于可能失败的清理操作(I/O 刷新、网络关闭、异步提交),公开显式的 close() 或 shutdown() 方法,返回 Result<(), Error>(或 impl Future),并在 Drop 中执行尽力而为的清理作为后备方案。“在 Drop 中消耗 self”的模式包括:使用 mem::take 的 Option<T> 新类型、对每个字段分别调用 mem::take,或使用 ManuallyDrop<T>。切勿在 Drop 中调用 block_on(...)(这会在异步运行时中导致死锁)。对于有作用域的状态变更(切换、还原、在 panic 时运行),使用通过 let _guard = ... 绑定的drop guard——切勿使用 let _ = ...,因为它会立即析构。scopeguard::defer! 是经过充分验证的方案;请注意,在 panic = "abort" 时不会运行 drop guard。有关显式析构函数模式,请参阅 references/coding-idioms.md 和 references/api-design.md。

异步 API

async fn 会降级为一个状态机,并返回匿名的 impl Future。通过 -> impl Future<...> + Send + '_(或显式指定与 &self 关联的生命周期),锁定公共 API 的 Send 性契约——-> impl Trait 中的自动 trait 传播是静默的,只要有一个 Rc<...> 或 std::sync::MutexGuard 在 .await 期间持有,整个 future 就会降级为 !Send,从而导致下游调用方出错。只有当 future 必须被生成到多线程执行器上时(例如 tokio::spawn),才添加 + 'static;在每个签名中一概添加 'static 会禁止从 &self 借用,这不是合适的默认做法。丢弃即取消:如果在轮询过程中丢弃 future,局部变量会被销毁,但不会运行任何清理逻辑。为每个公共 async fn 记录取消安全性:取消安全(recv,仅观察)与取消不安全(read_exact、write_all,以及任何持有跨轮询不变量的操作)。对于与运行时无关的库代码,接受 impl Future 或使用 futures crate 的原语——不要在内部生成任务,并记录所需的运行时。使用 std::pin::pin! 宏将局部 future 固定在栈上;只有在可以接受堆分配时才使用 Box::pin。

评论

支持 Markdown