The review lens

This is the cheat-sheet version of the 12 failure modes, organized by Rust concept. When you read a diff, mentally cycle through these. Most agent-written Rust bugs show up in one of these buckets.

Each row is: what concept is in play, what to watch for, the one-line prompt that surfaces the issue.

Ownership and borrowing

Watch forWhyPrompt
New .clone() callsOften a borrow would work; signals borrow-checker confusion"Justify each new clone. Replace with a borrow where possible."
Box::leak(...), Box::new(...).into_static()Memory leak to dodge a lifetime error"Replace this leak with a real lifetime or owned value."
'static appearing where it did not beforeLifetime shortcut that often lies about the contract"Name the real lifetime. Use 'a, 'src, or owned values."
Vec::clone(), String::clone() on hot pathsAllocations the agent did not notice"Profile or argue why this allocation is acceptable."

Errors

Watch forWhyPrompt
unwrap(), expect() in non-test codePanic site, contract violation"Replace with ? and a real error variant."
Box<dyn Error> or anyhow::Result in librariesLoses the typed error contract"Define a thiserror enum with concrete variants."
match chains rewrapping errorsBigger than ? for no reason"Use ? and add #[from] where natural."
Panic in From / TryFrom implsFrom says "infallible." Panic breaks that contract."Use TryFrom if conversion can fail."

Traits and generics

Watch forWhyPrompt
Box<dyn Trait> where a generic T: Trait would workDynamic dispatch + heap allocation, often unnecessary"Why dynamic dispatch? Use generics unless heterogeneous."
New trait with Send + Sync not declaredAsync + multi-thread will reject it later"Declare the supertraits this will need: Send + Sync if cross-task."
async_trait missing on object-safe async traitWill not compile if used dynamically"Add #[async_trait] if this trait is used as dyn."

Async

Watch forWhyPrompt
std::sync::Mutex held across .awaitBlocks the runtime, deadlock risk"List the .await calls and which locks are held across each."
tokio::spawn with no JoinHandle returnedFire-and-forget loses error visibility"Capture the handle and .await it, or document the fire-and-forget intent."
Missing Send bound on async functionCannot be spawned on multi-thread runtime"Confirm everything held across .await is Send."
Hand-rolled Future implAlmost always wrong vs. async fn"Replace with async fn."
block_on inside an async functionDeadlocks the executor"Never call block_on inside async code. Use .await."

Unsafe

Watch forWhyPrompt
New unsafe blockBypasses the compiler. High-risk."State the invariants you uphold. Justify why safe alternatives do not work."
unsafe without // SAFETY: commentConvention violated; reviewer cannot verify"Add a // SAFETY: comment explaining the invariant."
unsafe impl Send / unsafe impl SyncCross-thread safety asserted by hand"Prove this type can actually be sent / shared safely."

Modules and visibility

Watch forWhyPrompt
Newly pub items that should be privatePublic surface should be tiny and intentional"Why is this pub? Could it be pub(crate) or private?"
mod foo; exposing internal helpersTest setup leaking into the public API"Use pub(crate) or move tests into the module."
Re-exports that flatten too muchHides where types live"Re-export only the public API. Keep internals namespaced."

Performance signals

Watch forWhyPrompt
Vec::clone() in a loopQuadratic allocation"Allocate once. Reuse the buffer."
String::from(...) in a hot pathHeap alloc, often replaceable with &str"Can this take &str instead?"
Boxing small typesHeap allocation for stack-sized data"Why is this boxed?"
Collecting iterators eagerly when a stream worksBuffers everything in memory"Can this stay an iterator until the consumer needs it?"

Testing

Watch forWhyPrompt
Only happy-path testsEvery Err variant should be tested"List every Err variant. Add a test per variant."
assert!(result.is_ok()) instead of assert_eq!Loses the actual error message on failure"Assert on the value, not just the discriminant."
Test that hides panics with should_panic blindlyCatches the wrong panic"If you use #[should_panic], set expected = ...."
New #[cfg(test)] mod tests with no testsEmpty module, wasted file"Either add tests now or remove the module."

Dependencies

Watch forWhyPrompt
New crate in Cargo.tomlAdds attack surface, license risk, build time"Justify the new dep. What from std or existing deps would have worked?"
Crate without default-features = false for a tiny usePulls in optional features you do not need"Disable default features. Enable only what we use."
Direct version pin to a SemVer-incompatible boundLocks ecosystem to your decision"Use a SemVer range."

The keyboard shortcut

This entire page collapses to one ripgrep one-liner you can keep in your shell history:

# Things to grep for in any agent-written Rust PR diff.
git diff main...HEAD -- '*.rs' | rg \
  '^\+.*(unsafe |\.unwrap\(\)|\.expect\(|Box<dyn Error|'\
'Arc<Mutex|Arc<RwLock|Box::leak|'\''static|'\
'\.clone\(\)|tokio::spawn|block_on)'

Read each line that prints. Each is a review checkpoint.