mcpField
R
rust-code-review

rust-code-review

github.com/existential-birds测试与质量8598 次安装12 周前更新
代码仓库

关于 rust-code-review

用于 Rust 代码审查的 Skill,引导审查者了解 crate 环境、变更代码及所有权、错误处理、并发和 unsafe 等问题类别。分阶段关卡要求提交发现前提供证据并校准严重性。

主要功能

  1. 01Edition 与依赖环境检查
  2. 02所有权与错误处理清单
  3. 03并发与接口审查
  4. 04unsafe 代码审查清单
  5. 05分阶段发现验证关卡

适用场景

  1. 01跨 Rust edition 审查代码变更
  2. 02检查所有权与异步行为
  3. 03提交前验证 unsafe 代码问题

安装

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

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

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

包含的文件

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

references/
async-concurrency.md
common-mistakes.md
concurrency-models.md
concurrency-primitives.md
error-handling.md
interface-design.md
lifetime-variance.md
lock-free-patterns.md
memory-ordering.md
ownership-borrowing.md
patterns-in-the-wild.md
types-layout.md
unsafe-deep.md

详细介绍

Rust 代码审查

审查工作流

按以下顺序操作,避免误报并发现特定版本的问题:

  1. 检查 Cargo.toml — 记录 Rust 版本(2018、2021、2024)以及是否设置了 MSRV。Rust 2024 版对 unsafe 语义、RPIT 生命周期捕获、临时值作用域和 ! 类型回退引入了破坏性变更。这决定了哪些模式适用。如果存在工作区结构,也要检查。
  2. 检查依赖项 — 记录关键 crate(thiserror 与 anyhow、tokio 功能、serde 功能)。这些信息有助于判断预期使用哪些模式。
  3. 扫描变更文件 — 阅读完整函数,而不仅仅是差异。许多 Rust 漏洞都隐藏在函数的所有权流转中。
  4. 逐项检查类别 — 按照下面的检查清单逐项检查,并在需要时加载参考资料。
  5. 提交前验证 — 完成下面的 关卡,包括验证协议关卡,再提交发现。

关卡

这些步骤有先后顺序:不要跳步进行“脑内验证”。每一步都有客观的通过条件,可通过检查磁盘上的文件和自己的阅读路径来满足。

  1. Crate 上下文 — 在参考特定版本的检查清单条目(Rust 2024、与 MSRV 相关的 API)或依赖项假设之前。通过条件: 打开相关的 Cargo.toml(包或工作区清单),并能用一行说明 edition 和 rust-version(如果已设置)。
  2. 扩展阅读 — 在报告 Major 或 Critical 发现之前。通过条件: 阅读包含所引用代码行的完整函数、unsafe 代码块或 impl/trait 项(而不只是差异片段)。
  3. 严重性匹配 — 在报告中的每条发现之前。通过条件: Severity 标签符合该问题类别对应的 Severity Calibration,或者使用 Informational 并给出一行理由。
  4. 验证协议 — 在完成报告之前。通过条件: 已加载 review-verification-protocol 技能,并完成其中适用于本次审查的每个步骤(不要用含糊的“我检查过了”代替)。

输出格式

按以下格式报告发现:

[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.

快速参考

问题类型参考
所有权转移、借用、生命周期、克隆陷阱、迭代器references/ownership-borrowing.md
生命周期变型、协变/不变、内存区域references/lifetime-variance.md
Result/Option 处理、thiserror、anyhow、不透明错误与枚举错误、使用 ? 延迟清理references/error-handling.md
异步陷阱、Send/Sync 限制、poll 契约、Pin 机制、取消安全性references/async-concurrency.md
Send/Sync 语义、原子操作、内存顺序、锁模式references/concurrency-primitives.md
内存顺序决策树、栅栏、ABA、凭空出现references/memory-ordering.md
手写自旋锁、通道、Arc、seqlock、CAS 重试模式references/lock-free-patterns.md
共享内存与工作线程池及 Actor 设计、异步与线程、竞态条件与数据竞争references/concurrency-models.md
类型布局、对齐、repr、PhantomData、泛型与 dyn Trait、宽指针、自动 Trait 泄漏references/types-layout.md
对象安全、符合人体工学的 Trait 实现、Deref 规范、可失败的析构函数、隐藏契约、is_normalreferences/interface-design.md
索引指针、drop 守卫、扩展 Trait、crate 前置模块references/patterns-in-the-wild.md
不安全代码、API 设计、derive 模式、clippy 模式references/common-mistakes.md
有效性与安全性、drop 检查、may_dangle、来源信息、不安全代码中的 panic 安全、MaybeUninit、Mirireferences/unsafe-deep.md

如需有关性能、指针类型、类型状态、clippy 配置、迭代器、泛型和文档的开发指导,请加载 rust-best-practices 技能。

审查清单

所有权与借用
  • 没有为绕过借用检查器而进行不必要的 .clone()(以此掩盖设计问题)
  • 循环内没有 .clone()——优先在迭代器上使用 .cloned() 或 .copied()
  • 不通过克隆来避免生命周期注解(显式取得所有权或重构代码)
  • 引用具有恰当的生命周期(较短的生命周期可行时,不使用过宽的 'static)
  • Edition 2024:RPIT(-> impl Trait)默认捕获作用域内的所有生命周期;使用 + use<'a> 精确控制捕获范围
  • 函数参数优先使用 &str 而非 String,使用 &[T] 而非 Vec<T>
  • 灵活的 API 参数使用 impl AsRef<T> 或 Into<T>
  • 没有悬垂引用或移动后使用
  • 仅在确实需要共享可变性时使用内部可变性(Cell、RefCell、Mutex)
  • 小型类型(≤24 字节)派生 Copy 并按值传递
  • 所有权不明确时使用 Cow<'_, T>
  • 集合转换优先使用迭代器链,而非基于索引的循环
  • 不要过早调用 .collect()——消费者接受迭代器时,直接传入迭代器
  • 求和优先使用 .sum() 而非 .fold()(编译器优化效果更好)
  • 回退逻辑涉及分配时,使用 _or_else 变体
  • Edition 2024:if let 临时值会在 if let 结束时析构——若代码依赖临时值持续到 else 分支,需重构
  • Edition 2024:Box<[T]> 实现了 IntoIterator——优先直接迭代,而非先调用 into_vec()
错误处理
  • 对可恢复错误使用 Result<T, E>,而非 panic!/unwrap/expect
  • 错误类型提供上下文(使用带有 #[error("...")] 的 thiserror 或手动实现 Display)
  • 使用 ? 运算符时,提供适当的 From 实现或使用 .map_err()
  • 仅在测试、示例或可证明安全的上下文中使用 unwrap() / expect()
  • 错误变体足够具体,便于调用方采取行动
  • 在应用中使用 anyhow,在库中使用 thiserror(或明确说明采用其他方案的理由)
  • 当回退操作涉及分配时,使用 _or_else 变体(ok_or_else、unwrap_or_else)
  • 使用 let-else 在失败时提前返回(let Ok(x) = expr else { return ... })
  • 使用 inspect_err 记录错误,使用 map_err 转换错误
Trait 和类型
  • Trait 保持精简且内聚(单一职责)
  • 为类型选择恰当的 derive 宏(正确使用 Clone、Debug、PartialEq)
  • 使用 newtype 避免原始类型痴迷(例如,使用 struct UserId(Uuid),而非直接使用 Uuid)
  • From/Into 实现应无损且不可失败;可能失败的转换使用 TryFrom
  • 当不应允许外部实现时,使用封闭 Trait
  • 在适用时提供默认实现
  • 验证跨线程共享的类型满足 Send + Sync 约束
  • 在公共 Trait 上使用 #[diagnostic::on_unimplemented],以便用户忘记实现时提供清晰的错误消息
接口设计

详细指南:references/interface-design.md

  • 面向 dyn 的 Trait 中的方法不使用按值传递的 Self、泛型参数或关联常量(或通过 where Self: Sized 限定)
  • 新 Trait 应为 &T、&mut T 和 Box<T> 提供 blanket impl,以支持引用和智能指针参数
  • 可迭代类型应为 &Self 和 &mut Self 实现 IntoIterator,而不仅是为 Self 实现
  • Deref 仅用于透明转发,绝不用于“继承”——固有方法歧义是一类真实的 Bug
  • 可能失败的清理操作应使用返回 Result 的显式 close()/shutdown();Drop 仅作为尽力而为的后备方案
  • 不要在 Drop 中调用 block_on(...) 或启动新的运行时(这会在异步运行时中导致死锁)
  • 公共类型应提供编译时测试 fn is_normal<T: Sized + Send + Sync + Unpin>() {},以便在构建时发现自动 Trait 回归
  • 标记公共 API 中重新导出的外部类型——下游的主版本升级会成为此 crate 的破坏性变更
  • Getter 方法遵循 fn name(&self) 这一约定,而非 fn get_name(&self)(将 get_* 保留给返回 Option 或执行特殊查找的方法)
  • as_* 表示低开销的引用到引用转换,to_* 可能分配,into_* 会消耗自身——确认成本与前缀相符
  • 为每个公共类型考虑标准派生(Debug、Clone、Default、PartialEq、Eq、Hash);仅在类型确实轻量且具有值语义时使用 Copy(之后移除它会造成破坏性变更)
实际应用中的模式

详细指南:references/patterns-in-the-wild.md

  • 索引指针图使用生成索引(slotmap::DefaultKey、petgraph::NodeIndex)——Vec::swap_remove 后,裸 usize 会变成孤儿索引
  • Drop 守卫绑定到 let _guard = ...,绝不使用 let _ = ...(后者会立即销毁,而非在作用域结束时销毁)
  • 不要在 panic = "abort" 下依赖 Drop 守卫进行清理(不会运行析构函数)
  • 仅当类型来自外部时才使用扩展 Trait;对于自有类型,直接使用固有 impl
  • 扩展 Trait 的方法不得遮蔽同一类型上已有的常用方法(会造成调用位置歧义)
  • 将 crate 的 prelude 模块新增内容视为 semver 次版本变更(RFC 1105);尽可能留到主版本发布时添加
并发设计(模型)

详细指南:references/concurrency-models.md

  • 在设计中明确说明并发模型(共享内存、工作池或 Actor);所用原语与模型相符
  • Mutex 临界区简短、可测量,且不包含 I/O、网络调用或 .await 点
  • 工作池队列有界;明确说明背压策略
  • Actor 邮箱通道的容量与预期负载相符;检查跨 Actor 循环是否会导致死锁
  • 不将 async 与并行混为一谈——join! 会在单个线程上交错执行;只有 tokio::spawn(或多线程运行时中的等效机制)才会并行执行
  • 考虑将热点原子变量上的 CAS 重试循环替换为 fetch_add/分片(CAS 在竞争下的复杂度为 O(N²))
  • 不使用 println!/dbg! 调试竞态条件(Stdout mutex 会改变竞态状况)
不安全代码
  • unsafe 块附有安全性注释,说明相关不变量
  • unsafe 用量最少——块内只包含真正不安全的操作
  • 安全性不变量有文档说明,且由周边安全代码维护
  • 不存在未定义行为(空指针解引用、数据竞争、无效内存访问)
  • unsafe trait 实现说明为何满足其契约
  • Edition 2024:unsafe fn 函数体在不安全操作周围使用显式的 unsafe {} 块(unsafe_op_in_unsafe_fn 为 deny)
  • Edition 2024:extern "C" {} 块写作 unsafe extern "C" {}
  • Edition 2024:#[no_mangle] 和 #[export_name] 写作 #[unsafe(no_mangle)] 和 #[unsafe(export_name)]
并发(内存排序与无锁模式)

详细指南:references/memory-ordering.md、references/lock-free-patterns.md

  • 跨线程共享的类型具有正确的 Send/Sync 约束;unsafe impl Send/Sync 附有注释,说明相关不变量
  • 每个原子操作都对应一个明确命名的先行发生关系(spawn/join、同一原子变量上的 Release/Acquire,或内存栅栏);Release 发布数据,Acquire 观察数据
  • 默认不使用 SeqCst——只有在两个或更多独立原子变量需要单一全局全序时才使用,并通过注释说明这一要求
  • 不使用 store(.., Acquire)/load(.., Release)/load(.., AcqRel)(这些排序与其所在位置的类型不匹配)
  • 不使用 Relaxed 发布或观察非原子数据(使用 Release/Acquire)
  • 在重试循环中使用 compare_exchange_weak;compare_exchange 仅用于一次性更新;获取临界区时,成功排序至少为 Acquire
  • 手写自旋锁的忙等循环中包含 std::hint::spin_loop(),采用指数退避,并最终调用 thread::yield_now();除非有文档说明不适合使用 Mutex 的原因,否则不要在普通用户态二进制程序中使用
  • 手写 Arc 克隆时使用 Relaxed,递减时使用 Release,且在最后一次递减时使用 fence(Acquire),并包含溢出保护
  • 使用 Weak 打破 Arc<Mutex<...>> 循环;检查是否可将 Arc<Mutex<Copy>> 替换为 Arc<AtomicT>
  • 手写无锁原语包含 #[cfg(loom)] 测试模块和可由 Miri 运行的测试(不使用一概而论的 cfg_attr(miri, ignore))
  • 新代码优先使用 OnceLock/LazyLock,而非 once_cell/lazy_static(MSRV ≥ 1.80)
  • 不在 .await 期间持有 MutexGuard(使用 tokio::sync::Mutex 或先释放 guard)
  • 对竞争缓存行上的热点原子变量使用 CachePadded 或 #[repr(align(64))] 包装,以避免伪共享
  • 共享可变状态通过 UnsafeCell<T> 实现(不使用裸 *mut T,也不将 & 转换为 &mut)
  • 基于指针的 CAS(AtomicPtr<Node>)使用 epoch/hazard pointer/tagged pointer 回收机制;考虑 ABA 风险
命名与风格
  • 类型使用 PascalCase,函数/方法使用 snake_case,常量使用 SCREAMING_SNAKE_CASE
  • 模块使用 snake_case
  • 布尔值返回方法使用 is_、has_、can_ 前缀
  • 构建器模式的方法接收并返回 self(而非 &mut self),以支持链式调用
  • 公共项包含文档注释(///)
  • 对于忽略返回值很可能导致错误的函数,使用 #[must_use]
  • 导入顺序:std → 外部 crate → 工作区 → crate/super
  • 抑制 lint 时,优先使用 #[expect(clippy::...)] 而非 #[allow(...)]
性能

详细指南: rust-best-practices 技能(references/performance.md)

  • 热路径中没有不必要的分配(优先使用 &str 而非 String,使用 &[T] 而非 Vec<T>)
  • collect() 的类型已指定或可推断
  • 转换集合时优先使用迭代器,而非带索引的循环
  • 已知大小时使用 Vec::with_capacity()
  • 没有多余的 .to_string() / .to_owned() 链式调用
  • 直接传递迭代器可行时,不使用中间的 .collect()
  • 求和时优先使用 .sum(),而非 .fold()
  • 除非需要灵活性,否则使用静态分发(impl Trait),而非动态分发(dyn Trait)
Clippy 配置

详细指南: rust-best-practices 技能(references/clippy-config.md)

  • 在工作区级别的 Cargo.toml 中配置 lint([workspace.lints.clippy] 或 [lints.clippy])
  • 使用 #[expect(clippy::lint)],而非 #[allow(...)]——当抑制规则不再适用时会发出警告
  • 抑制任何 lint 时均附有说明注释
  • 强制启用关键 lint:redundant_clone、large_enum_variant、needless_collect、perf 组
  • cargo clippy --all-targets --all-features -- -D warnings 通过
  • 为库 crate 启用文档 lint(missing_docs、broken_intra_doc_links)
类型状态模式

详细指南: rust-best-practices 技能(references/type-state-pattern.md)

  • 使用 PhantomData<State> 实现零成本的编译时状态机(而非运行时枚举/布尔值)
  • 状态转换消耗 self 并返回新的状态类型(防止重用旧状态)
  • 每种状态仅提供适用的方法(无效操作会导致编译错误)
  • 在能够提升安全性的场景使用该模式(包含必需字段的构建器、连接状态、工作流)
  • 不要过度用于简单状态(需要运行时灵活性时,简单枚举即可)

严重性分级

严重(阻止合并)
  • unsafe 代码存在不健全的不变量或未定义行为
  • 使用后释放或悬垂引用模式
  • 生产代码中对用户输入或外部数据调用 unwrap()
  • 数据竞争(并发修改时未进行同步)
  • 控制其他共享数据的原子操作使用了错误的内存顺序(数据竞争)
  • 通过没有弱引用的循环 Arc<Mutex<...>> 导致内存泄漏
重大(应修复)
  • 返回错误时缺少上下文(相当于直接 return err)
  • 热路径中的 .clone() 掩盖了所有权设计问题
  • 跨线程使用的类型缺少 Send/Sync 约束
  • 库代码对可恢复的错误调用 panic!
  • 过于宽泛的 'static 生命周期掩盖了 API 设计问题
轻微(可考虑修复)
  • 公共项缺少文档注释
  • 参数使用 String,而 &str 或 impl AsRef<str> 也适用
  • 应使用派生宏的类型缺少相应宏
  • Cargo.toml 中存在未使用的功能标志
  • 迭代器链不够理想(多次分配,而一次即可满足需求)
参考信息(仅作提示)
  • 建议为领域建模引入 newtype
  • trait 设计重构思路
  • 未经测量验证的性能优化
  • 建议添加 #[must_use] 或 #[non_exhaustive]

何时加载参考资料

  • 审查所有权、借用、生命周期、克隆陷阱 → ownership-borrowing.md
  • 审查生命周期方差、协变/不变、多个生命周期参数 → lifetime-variance.md
  • 审查 Result/Option 处理、错误类型、不透明类型与枚举类型、延迟清理、Error trait 实现 → error-handling.md
  • 审查异步代码、poll 契约、Pin 机制、取消安全性、跨运行时 → async-concurrency.md
  • 审查并发设计决策(共享内存、工作池还是 actor,异步还是线程)、数据竞争与竞态条件 → concurrency-models.md
  • 审查 Send/Sync、原子操作、互斥锁、锁模式 → concurrency-primitives.md
  • 审查内存排序决策、栅栏、ABA、凭空出现问题 → memory-ordering.md
  • 审查手写自旋锁、通道、Arc、seqlock、CAS 重试模式 → lock-free-patterns.md
  • 审查类型布局、对齐、repr、PhantomData、宽指针、自动 trait 泄漏、Sized/?Sized → types-layout.md
  • 审查接口设计——对象安全、符合人体工程学的 blanket impl、Deref 使用规范、可能失败/阻塞的析构函数、隐藏契约、命名 → interface-design.md
  • 审查索引指针图、drop guard、扩展 trait、crate 前导模块 → patterns-in-the-wild.md
  • 审查不安全代码、API 设计、derive 宏、clippy 模式 → common-mistakes.md
  • 审查有效性与安全性、drop check + may_dangle、来源信息、不安全代码中的 panic 安全性、MaybeUninit → unsafe-deep.md
  • 审查性能、指针类型、类型状态、泛型、迭代器、文档 → rust-best-practices 技能

有效模式(不要标记)

以下是可接受的 Rust 模式——报告这些问题会浪费开发者的时间:

  • 测试中的 .clone() —— 测试代码中清晰度优先于性能
  • 测试和示例中的 unwrap() —— 有意在失败时 panic 的场景下可以接受
  • 简单二进制程序中的 Box<dyn Error> —— 并非每个应用都需要自定义错误类型
  • 结构体中的 String 字段 —— 结构体中使用拥有所有权的数据是正确的;&str 字段需要生命周期参数
  • 开发期间的 #[allow(dead_code)] —— 迭代开发期间很常见
  • 新代码中的 todo!() / unimplemented!() —— 开发期间有效的占位符
  • 附带清晰说明的 .expect("reason") —— 可自行说明用途,适用于不变量
  • 测试模块中的 use super::* —— #[cfg(test)] 模块的标准模式
  • 复杂类型的类型别名 —— type Result<T> = std::result::Result<T, MyError> 是惯用写法
  • 返回位置上的 impl Trait —— 零开销抽象,是标准模式
  • Turbofish 语法 —— 类型推断需要帮助时,collect::<Vec<_>>() 是惯用写法
  • 为有意不使用的变量添加 _ 前缀 —— 符合编译器约定
  • 附有理由的 #[expect(clippy::...)] —— 可自动清理的 lint 抑制
  • Arc::clone(&arc) —— 显式克隆 Arc 是惯用且推荐的写法
  • 异步代码中用于短临界区的 std::sync::Mutex —— Tokio 文档推荐这样使用
  • 遍历迭代器的 for 循环 —— 需要提前退出或产生副作用时使用
  • trait 定义中的 async fn —— 自 1.75 起稳定;只有在需要 dyn Trait 或 MSRV 低于 1.75 时才需要 async-trait crate
  • 来自 std 的 LazyCell / LazyLock —— 自 1.80 起稳定;新代码中可替代 once_cell 和 lazy_static
  • + use<'a, T> 精确捕获语法 —— Edition 2024 语法,用于控制 RPIT 生命周期捕获

上下文相关规则

仅在满足特定条件时标记以下问题:

问题仅在以下情况下标记
缺少错误上下文错误跨越模块边界时未提供上下文
不必要的 .clone()出现在热点路径或重复调用中,而非测试/设置代码中
缺少文档注释条目为 pub,且不在 #[cfg(test)] 模块中
使用 unwrap()出现在生产代码路径中,而非测试/示例/可证明安全的代码中
缺少 Send + Sync类型确实会跨线程/任务边界共享
生命周期范围过宽较短的生命周期可行,且 API 为公开 API
缺少 #[must_use]函数返回的值通常会被调用者忽略
过时的 #[allow] 抑制为实现可自动清理的 lint 管理,应使用 #[expect]
缺少 Copy 派生类型不超过 24 字节,所有字段均实现 Copy,且经常使用
Edition 2024:! 类型回退对 Result<T, !> 进行匹配,或在假定回退为 () 的发散表达式中使用——现在 ! 会回退为 !,而非 ()
Edition 2024:r#gen 标识符代码将 gen 用作标识符——在 Edition 2024 中必须写作 r#gen(保留关键字)

提交问题前

请满足 Gates § 验证流程(步骤 4)的要求。报告任何问题前,请加载并遵循 review-verification-protocol 技能。

评论

支持 Markdown