name: rust-review description: Review Rust code for correctness, idiom, and consistency — not just lint. Covers error handling, ownership, API design, naming, async pitfalls, unsafe, and perf. Read-only — surfaces findings with file:line, does NOT auto-fix.
rust-review
When the user asks to review Rust code, lint, or "check this". Read-only:
surface findings with file:line, grouped by severity, and propose fixes
inline — do NOT edit until the user confirms. Do NOT run cargo fix --clippy (it rewrites code).
Pass 1 — mechanical (fast, do first)
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
Clippy catches a huge amount; report every finding with file:line. But
clippy is the floor, not the review — a clippy-clean diff can still be
unidiomatic, inconsistent, or wrong. The manual passes below are the
actual review.
Pass 2 — correctness (highest severity)
- Error handling: no
unwrap()/expect()/panic!/unreachable!/ array-index[i]on non-test, non-invariant paths. Flag each; the idiomatic fix is?with.ok_or(...)/.ok_or_elseforOptionand a real error type forResult.expectis OK only with a message that documents a genuine invariant. - Swallowed errors:
let _ = fallible();,.ok()that drops aResult,if let Ok(x) = ... {}with noelse. Flag silent drops. - Integer/
ascasts:astruncates silently. Flagason values that can exceed the target (usetry_into()+ handle the error, oru32::fromfor widening). Flagasused to drop signedness. unsafe: everyunsafeblock needs a// SAFETY:comment that actually justifies the invariants. Flag any without one, and any whose comment doesn't hold.- Float/
PartialEqfoot-guns,unwrapon locks (.lock().unwrap()hides poisoning — at least note it). .awaitwhile holding astd::syncguard /RefCellborrow — blocks the executor or panics. High-severity async correctness bug.
Pass 3 — idiom
- Iterators over index loops:
for i in 0..v.len() { v[i] }→for x in &v/.iter().enumerate(). Flag manual index walking,while letthat should be afor, andloop { ... break }that's a plain iterator chain. - Combinators where they read better:
match opt { Some(x) => f(x), None => default }→.map_or/.unwrap_or_else; nestedmatchonResult→?. Don't push this to the point of unreadable chains. - Borrowing over cloning: flag
.clone()/.to_vec()/.to_string()reached for to dodge the borrow checker. Ask whether a&/&str/&[T]or restructuring removes it. - Param types:
&strnot&String,&[T]not&Vec<T>,impl AsRef<Path>for path inputs,impl Into<String>when the fn takes ownership. Flag over-restrictive signatures. - Construction:
From/TryFromnot ad-hocto_x()converters;Defaultwhere it fits; builder for many-optional-field structs. - String building:
format!in a hot loop →write!into a reused buffer; repeatedpush_str→concat/joinwhere clearer. - Prefer
if let/let ... elseover single-armmatch.
Pass 4 — API & types (for anything pub)
- Domain newtypes over bare
u64/Stringids ("parse, don't validate" at the boundary). #[must_use]onResult-returning and builder methods;#[non_exhaustive]on enums/structs likely to grow.- Public error types: a
thiserrorenum, NOTBox<dyn Error>orString(callers can't match on those).anyhowonly at binary boundaries, not in a library's public API. - Trait choices: implement
From(getsIntofree); deriveDebug/Clone/PartialEqwhere reasonable;Copyonly on small POD. - Gate
serdederives behind a feature in libraries.
Pass 5 — consistency (within THIS codebase)
Idiom is partly local — match what the surrounding code already does:
- Naming:
snake_casefns/vars,CamelCasetypes,SCREAMING_CASEconsts; getters asfoo()notget_foo(); conversions namedas_/to_/into_per their cost convention. Flag drift from the file's existing names. - Error strategy: does the new code use the same error type / crate
(
thiserrorvsanyhow) as its module? Flag a new ad-hoc strategy. - Logging: same facade as the rest of the crate (
tracingvslog); flag a strayprintln!/eprintln!for diagnostics. - Module layout, import grouping, re-export style: match the crate's established pattern rather than introducing a new one.
- Test style: inline
#[cfg(test)] mod testsvstests/— follow the crate's convention.
Pass 6 — performance (flag, don't micro-optimize)
- Needless allocation in hot paths (
collect()then iterate again;Vecwhere an iterator suffices); missingVec::with_capacity(n)whennis known;.clone()of large owned data per loop iteration. - Large enum variants bloating every value (box the big variant).
- Only raise perf items that are clearly on a hot path or obviously wasteful — don't bikeshed cold-path allocations.
Output
Group findings by severity (correctness > idiom/API > consistency >
perf), each as file:line — problem — suggested fix. If a pass is
clean, say so explicitly. Cross-check against the repo's AGENTS.md and
its existing conventions. Don't narrow to one crate unless asked.