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.
Watch for Why Prompt New .clone() calls Often 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."
Watch for Why Prompt 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 impls From says "infallible." Panic breaks that contract."Use TryFrom if conversion can fail."
Watch for Why Prompt 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 declared Async + 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."
Watch for Why Prompt 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 function Cannot be spawned on multi-thread runtime "Confirm everything held across .await is Send." Hand-rolled Future impl Almost 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."
Watch for Why Prompt New unsafe block Bypasses 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."
Watch for Why Prompt Newly pub items that should be private Public 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 much Hides where types live "Re-export only the public API. Keep internals namespaced."
Watch for Why Prompt 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 types Heap allocation for stack-sized data "Why is this boxed?" Collecting iterators eagerly when a stream works Buffers everything in memory "Can this stay an iterator until the consumer needs it?"
Watch for Why Prompt Only happy-path tests Every 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 blindly Catches the wrong panic "If you use #[should_panic], set expected = ...." New #[cfg(test)] mod tests with no tests Empty module, wasted file "Either add tests now or remove the module."
Watch for Why Prompt New crate in Cargo.toml Adds 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 use Pulls in optional features you do not need "Disable default features. Enable only what we use." Direct version pin to a SemVer-incompatible bound Locks ecosystem to your decision "Use a SemVer range."
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.