Rust Code Review
Review Workflow
Follow this sequence to avoid false positives and catch edition-specific issues:
- 1. Check
Cargo.toml — Note the Rust edition (2018, 2021, 2024) and MSRV if set. Edition 2024 introduces breaking changes to unsafe semantics, RPIT lifetime capture, temporary scoping, and ! type fallback. This determines which patterns apply. Check workspace structure if present. - Check dependencies — Note key crates (thiserror vs anyhow, tokio features, serde features). These inform which patterns are expected.
- Scan changed files — Read full functions, not just diffs. Many Rust bugs hide in ownership flow across a function.
- Check each category — Work through the checklist below, loading references as needed.
- Verify before reporting — Load beagle-rust:review-verification-protocol before submitting findings.
Output Format
Report findings as:
CODEBLOCK0
Quick Reference
| Issue Type | Reference |
|---|
| Ownership transfers, borrowing, lifetimes, clone traps, iterators | references/ownership-borrowing.md |
| Lifetime variance, covariance/invariance, memory regions |
references/lifetime-variance.md |
| Result/Option handling, thiserror, anyhow, error context, Error trait |
references/error-handling.md |
| Async pitfalls, Send/Sync bounds, runtime blocking |
references/async-concurrency.md |
| Send/Sync semantics, atomics, memory ordering, lock patterns |
references/concurrency-primitives.md |
| Type layout, alignment, repr, PhantomData, generics vs dyn Trait |
references/types-layout.md |
| Unsafe code, API design, derive patterns, clippy patterns |
references/common-mistakes.md |
| Safety contracts, raw pointers, MaybeUninit, soundness, Miri |
references/unsafe-deep.md |
For development guidance on performance, pointer types, type state, clippy config, iterators, generics, and documentation, use the beagle-rust:rust-best-practices skill.
Review Checklist
Ownership and Borrowing
- - [ ] No unnecessary
.clone() to silence the borrow checker (hiding design issues) - [ ] No
.clone() inside loops — prefer .cloned() or .copied() on iterators - [ ] No cloning to avoid lifetime annotations (take ownership explicitly or restructure)
- [ ] References have appropriate lifetimes (not overly broad
'static when shorter lifetime works) - [ ] Edition 2024: RPIT (
-> impl Trait) captures all in-scope lifetimes by default; use + use<'a> for precise capture control - [ ]
&str preferred over String, &[T] over Vec<T> in function parameters - [ ]
impl AsRef<T> or Into<T> used for flexible API parameters - [ ] No dangling references or use-after-move
- [ ] Interior mutability (
Cell, RefCell, Mutex) used only when shared mutation is genuinely needed - [ ] Small types (≤24 bytes) derive
Copy and are passed by value - [ ]
Cow<'_, T> used when ownership is ambiguous - [ ] Iterator chains preferred over index-based loops for collection transforms
- [ ] No premature
.collect() — pass iterators directly when the consumer accepts them - [ ]
.sum() preferred over .fold() for summation (compiler optimizes better) - [ ]
_or_else variants used when fallbacks involve allocation - [ ] Edition 2024:
if let temporaries drop at end of the if let — code relying on temporaries living through the else branch needs restructuring - [ ] Edition 2024:
Box<[T]> implements IntoIterator — prefer direct iteration over into_vec() first
Error Handling
- - [ ]
Result<T, E> used for recoverable errors, not panic!/unwrap/ INLINECODE33 - [ ] Error types provide context (thiserror with
#[error("...")] or manual Display) - [ ]
? operator used with proper From implementations or INLINECODE38 - [ ]
unwrap() / expect() only in tests, examples, or provably-safe contexts - [ ] Error variants are specific enough to be actionable by callers
- [ ]
anyhow used in applications, thiserror in libraries (or clear rationale for alternatives) - [ ]
_or_else variants used when fallbacks involve allocation (ok_or_else, unwrap_or_else) - [ ]
let-else used for early returns on failure (let Ok(x) = expr else { return ... }) - [ ]
inspect_err used for error logging, map_err for error transformation
Traits and Types
- - [ ] Traits are minimal and cohesive (single responsibility)
- [ ]
derive macros appropriate for the type (Clone, Debug, PartialEq used correctly) - [ ] Newtypes used to prevent primitive obsession (e.g.,
struct UserId(Uuid) not bare Uuid) - [ ]
From/Into implementations are lossless and infallible; TryFrom for fallible conversions - [ ] Sealed traits used when external implementations shouldn't be allowed
- [ ] Default implementations provided where they make sense
- [ ]
Send + Sync bounds verified for types shared across threads - [ ]
#[diagnostic::on_unimplemented] used on public traits to provide clear error messages when users forget to implement them
Unsafe Code
- - [ ]
unsafe blocks have safety comments explaining invariants - [ ]
unsafe is minimal — only the truly unsafe operation is inside the block - [ ] Safety invariants are documented and upheld by surrounding safe code
- [ ] No undefined behavior (null pointer deref, data races, invalid memory access)
- [ ]
unsafe trait implementations justify why the contract is upheld - [ ] Edition 2024:
unsafe fn bodies use explicit unsafe {} blocks around unsafe ops (unsafe_op_in_unsafe_fn is deny) - [ ] Edition 2024:
extern "C" {} blocks written as INLINECODE68 - [ ] Edition 2024:
#[no_mangle] and #[export_name] written as #[unsafe(no_mangle)] and INLINECODE72
Naming and Style
- - [ ] Types are
PascalCase, functions/methods snake_case, constants INLINECODE75 - [ ] Modules use INLINECODE76
- [ ]
is_, has_, can_ prefixes for boolean-returning methods - [ ] Builder pattern methods take and return
self (not &mut self) for chaining - [ ] Public items have doc comments (
///) - [ ]
#[must_use] on functions where ignoring the return value is likely a bug - [ ] Imports ordered: std → external crates → workspace → crate/super
- [ ]
#[expect(clippy::...)] preferred over #[allow(...)] for lint suppression
Performance
Detailed guidance: beagle-rust:rust-best-practices skill (references/performance.md)
- - [ ] No unnecessary allocations in hot paths (prefer
&str over String, &[T] over Vec<T>) - [ ]
collect() type is specified or inferable - [ ] Iterators preferred over indexed loops for collection transforms
- [ ]
Vec::with_capacity() used when size is known - [ ] No redundant
.to_string() / .to_owned() chains - [ ] No intermediate
.collect() when passing iterators directly works - [ ]
.sum() preferred over .fold() for summation - [ ] Static dispatch (
impl Trait) used over dynamic (dyn Trait) unless flexibility required
Clippy Configuration
Detailed guidance: beagle-rust:rust-best-practices skill (references/clippy-config.md)
- - [ ] Workspace-level lints configured in
Cargo.toml ([workspace.lints.clippy] or [lints.clippy]) - [ ]
#[expect(clippy::lint)] used over #[allow(...)] — warns when suppression becomes stale - [ ] Justification comment present when suppressing any lint
- [ ] Key lints enforced:
redundant_clone, large_enum_variant, needless_collect, perf group - [ ]
cargo clippy --all-targets --all-features -- -D warnings passes - [ ] Doc lints enabled for library crates (
missing_docs, broken_intra_doc_links)
Type State Pattern
Detailed guidance: beagle-rust:rust-best-practices skill (references/type-state-pattern.md)
- - [ ]
PhantomData<State> used for zero-cost compile-time state machines (not runtime enums/booleans) - [ ] State transitions consume
self and return new state type (prevents reuse of old state) - [ ] Only applicable methods available per state (invalid operations are compile errors)
- [ ] Pattern used where it adds safety value (builders with required fields, connection states, workflows)
- [ ] Not overused for trivial state (simple enums are fine when runtime flexibility needed)
Severity Calibration
Critical (Block Merge)
- -
unsafe code with unsound invariants or undefined behavior - Use-after-free or dangling reference patterns
- INLINECODE117 on user input or external data in production code
- Data races (concurrent mutation without synchronization)
- Memory leaks via circular
Arc<Mutex<...>> without weak references
Major (Should Fix)
- - Errors returned without context (bare
return err equivalent) - INLINECODE120 masking ownership design issues in hot paths
- Missing
Send/Sync bounds on types used across threads - INLINECODE123 for recoverable errors in library code
- Overly broad
'static lifetimes hiding API design issues
Minor (Consider Fixing)
- - Missing doc comments on public items
- INLINECODE125 parameter where
&str or impl AsRef<str> would work - Derive macros missing for types that should have them
- Unused feature flags in INLINECODE128
- Suboptimal iterator chains (multiple allocations where one suffices)
Informational (Note Only)
- - Suggestions to introduce newtypes for domain modeling
- Refactoring ideas for trait design
- Performance optimizations without measured impact
- Suggestions to add
#[must_use] or INLINECODE130
When to Load References
- - Reviewing ownership, borrows, lifetimes, clone traps → ownership-borrowing.md
- Reviewing lifetime variance, covariance/invariance, multiple lifetime params → lifetime-variance.md
- Reviewing Result/Option handling, error types, Error trait impls → error-handling.md
- Reviewing async code, tokio usage, task management → async-concurrency.md
- Reviewing Send/Sync, atomics, memory ordering, mutexes, lock patterns → concurrency-primitives.md
- Reviewing type layout, alignment, repr, PhantomData, generics vs dyn → types-layout.md
- Reviewing unsafe code, API design, derive macros, clippy patterns → common-mistakes.md
- Reviewing safety contracts, raw pointers, MaybeUninit, soundness → unsafe-deep.md
- Reviewing performance, pointer types, type state, generics, iterators, documentation →
beagle-rust:rust-best-practices skill
Valid Patterns (Do NOT Flag)
These are acceptable Rust patterns — reporting them wastes developer time:
- -
.clone() in tests — Clarity over performance in test code unwrap() in tests and examples — Acceptable where panicking on failure is intentionalBox<dyn Error> in simple binaries — Not every application needs custom error typesString fields in structs — Owned data in structs is correct; &str fields require lifetime parameters#[allow(dead_code)] during development — Common during iterationtodo!() / unimplemented!() in new code — Valid placeholder during active development.expect("reason") with clear message — Self-documenting and acceptable for invariantsuse super::* in test modules — Standard pattern for #[cfg(test)] modules- Type aliases for complex types —
type Result<T> = std::result::Result<T, MyError> is idiomatic impl Trait in return position — Zero-cost abstraction, standard pattern- Turbofish syntax —
collect::<Vec<_>>() is idiomatic when type inference needs help _ prefix for intentionally unused variables — Compiler convention#[expect(clippy::...)] with justification — Self-cleaning lint suppressionArc::clone(&arc) — Explicit Arc cloning is idiomatic and recommendedstd::sync::Mutex for short critical sections in async — Tokio docs recommend thisfor loops over iterators — When early exit or side effects are neededasync fn in trait definitions — Stable since 1.75; async-trait crate only needed for dyn Trait or pre-1.75 MSRVLazyCell / LazyLock from std — Stable since 1.80; replaces once_cell and lazy_static for new code+ use<'a, T> precise capture syntax — Edition 2024 syntax for controlling RPIT lifetime capture
Context-Sensitive Rules
Only flag these issues when the specific conditions apply:
| Issue | Flag ONLY IF |
|---|
| Missing error context | Error crosses module boundary without context |
| Unnecessary INLINECODE159 |
In hot path or repeated call, not test/setup code |
| Missing doc comments | Item is
pub and not in a
#[cfg(test)] module |
|
unwrap() usage | In production code path, not test/example/provably-safe |
| Missing
Send + Sync | Type is actually shared across thread/task boundaries |
| Overly broad lifetime | A shorter lifetime would work AND the API is public |
| Missing
#[must_use] | Function returns a value that callers commonly ignore |
| Stale
#[allow] suppression | Should be
#[expect] for self-cleaning lint management |
| Missing
Copy derive | Type is ≤24 bytes with all-Copy fields and used frequently |
|
Edition 2024:
! type fallback | Match on
Result<T, !> or diverging expressions where
() fallback was assumed —
! now falls back to
! not
() |
|
Edition 2024:
r#gen identifier | Code uses
gen as an identifier — must be
r#gen in edition 2024 (reserved keyword) |
Before Submitting Findings
Load and follow beagle-rust:review-verification-protocol before reporting any issue.
Rust 代码审查
审查流程
按以下顺序操作以避免误报并捕获版本特定问题:
- 1. 检查 Cargo.toml — 注意 Rust 版本(2018、2021、2024)和 MSRV(如已设置)。2024 版本引入了对不安全语义、RPIT 生命周期捕获、临时作用域和 ! 类型回退的重大变更。这决定了哪些模式适用。如果存在工作区结构,请检查。
- 检查依赖项 — 注意关键 crate(thiserror vs anyhow、tokio 特性、serde 特性)。这些信息指示了预期的模式。
- 扫描变更文件 — 阅读完整函数,而不仅仅是差异。许多 Rust 错误隐藏在跨函数的所有权流中。
- 检查每个类别 — 按照下面的清单逐一检查,根据需要加载参考资料。
- 提交前验证 — 在提交发现之前加载 beagle-rust:review-verification-protocol。
输出格式
按以下格式报告发现:
text
[文件:行号] 问题标题
严重程度:严重 | 主要 | 次要 | 信息性
问题描述及其重要性说明。
快速参考
references/lifetime-variance.md |
| Result/Option 处理、thiserror、anyhow、错误上下文、Error trait |
references/error-handling.md |
| 异步陷阱、Send/Sync 约束、运行时阻塞 |
references/async-concurrency.md |
| Send/Sync 语义、原子操作、内存顺序、锁模式 |
references/concurrency-primitives.md |
| 类型布局、对齐、repr、PhantomData、泛型 vs dyn Trait |
references/types-layout.md |
| 不安全代码、API 设计、derive 模式、clippy 模式 |
references/common-mistakes.md |
| 安全契约、原始指针、MaybeUninit、健全性、Miri |
references/unsafe-deep.md |
有关性能、指针类型、类型状态、clippy 配置、迭代器、泛型和文档的开发指导,请使用 beagle-rust:rust-best-practices 技能。
审查清单
所有权和借用
- - [ ] 没有不必要的 .clone() 来绕过借用检查器(隐藏设计问题)
- [ ] 循环内没有 .clone() — 优先在迭代器上使用 .cloned() 或 .copied()
- [ ] 没有通过克隆来避免生命周期标注(显式获取所有权或重构)
- [ ] 引用具有适当的生命周期(当更短的生命周期可行时,不要过度使用 static)
- [ ] 2024 版本:RPIT(-> impl Trait)默认捕获所有作用域内的生命周期;使用 + use 进行精确捕获控制
- [ ] 函数参数中优先使用 &str 而非 String,&[T] 而非 Vec
- [ ] 使用 impl AsRef 或 Into 实现灵活的 API 参数
- [ ] 没有悬垂引用或移动后使用
- [ ] 仅在真正需要共享可变性时使用内部可变性(Cell、RefCell、Mutex)
- [ ] 小型类型(≤24 字节)派生 Copy 并按值传递
- [ ] 当所有权不明确时使用 Cow<, T>
- [ ] 集合转换优先使用迭代器链而非基于索引的循环
- [ ] 没有过早的 .collect() — 当消费者接受迭代器时直接传递
- [ ] 求和时优先使用 .sum() 而非 .fold()(编译器优化更好)
- [ ] 当回退涉及分配时使用 orelse 变体
- [ ] 2024 版本:if let 临时变量在 if let 结束时释放 — 依赖临时变量存活到 else 分支的代码需要重构
- [ ] 2024 版本:Box<[T]> 实现了 IntoIterator — 优先直接迭代而非先调用 intovec()
错误处理
- - [ ] 可恢复错误使用 Result,而非 panic!/unwrap/expect
- [ ] 错误类型提供上下文(thiserror 配合 #[error(...)] 或手动 Display)
- [ ] ? 运算符配合适当的 From 实现或 .maperr() 使用
- [ ] unwrap() / expect() 仅用于测试、示例或可证明安全的上下文
- [ ] 错误变体足够具体,可供调用者采取行动
- [ ] 应用程序中使用 anyhow,库中使用 thiserror(或使用替代方案有明确理由)
- [ ] 当回退涉及分配时使用 orelse 变体(okorelse、unwraporelse)
- [ ] 失败时使用 let-else 提前返回(let Ok(x) = expr else { return ... })
- [ ] 错误日志使用 inspecterr,错误转换使用 map_err
特性和类型
- - [ ] 特性最小且内聚(单一职责)
- [ ] 类型的 derive 宏适当(Clone、Debug、PartialEq 正确使用)
- [ ] 使用新类型防止原始类型痴迷(例如 struct UserId(Uuid) 而非裸 Uuid)
- [ ] From/Into 实现无损且不会失败;TryFrom 用于可能失败的转换
- [ ] 当不允许外部实现时使用密封特性
- [ ] 在合理的情况下提供默认实现
- [ ] 验证跨线程使用的类型的 Send + Sync 约束
- [ ] 在公共特性上使用 #[diagnostic::on_unimplemented],当用户忘记实现时提供清晰的错误消息
不安全代码
- - [ ] unsafe 块有解释不变量的安全注释
- [ ] unsafe 最小化 — 只有真正不安全的操作在块内
- [ ] 安全不变量有文档记录并由周围的安全代码维护
- [ ] 没有未定义行为(空指针解引用、数据竞争、无效内存访问)
- [ ] unsafe 特性实现证明为什么契约得到维护
- [ ] 2024 版本:unsafe fn 体在不安全操作周围使用显式 unsafe {} 块(unsafeopinunsafefn 为 deny)
- [ ] 2024 版本:extern C {} 块写为 unsafe extern C {}
- [ ] 2024 版本:#[nomangle] 和 #[exportname] 写为 #[unsafe(nomangle)] 和 #[unsafe(exportname)]
命名和风格
- - [ ] 类型使用 PascalCase,函数/方法使用 snakecase,常量使用 SCREAMINGSNAKECASE
- [ ] 模块使用 snakecase
- [ ] 返回布尔值的方法使用 is、has、can 前缀
- [ ] Builder 模式方法接受并返回 self(而非 &mut self)以支持链式调用
- [ ] 公共项有文档注释(///)
- [ ] 忽略返回值可能是错误时在函数上使用 #[mustuse]
- [ ] 导入顺序:std → 外部 crate → 工作区 → crate/super
- [ ] lint 抑制优先使用 #[expect(clippy::...)] 而非 #[allow(...)]
性能
详细指导:beagle-rust:rust-best-practices 技能(references/performance.md)
- - [ ] 热路径中没有不必要的分配(优先使用 &str 而非 String,&[T] 而非 Vec)
- [ ] collect() 类型已指定或可推断
- [ ] 集合转换优先使用迭代器而非索引循环
- [ ] 当大小已知时使用 Vec::withcapacity()
- [ ] 没有冗余的 .tostring() / .to_owned() 链
- [ ] 当直接传递迭代器可行时,没有中间的 .collect()
- [ ] 求和时优先使用 .sum() 而非 .