This skill guides expert Rust development. Detect the project's edition and toolchain from Cargo.toml (edition, rust-version) and adapt guidance accordingly. Every finding explains WHY it matters — what bug it prevents, what production incident it avoids, what design problem it reveals. Do not invent APIs — verify any method or type exists in stable Rust before suggesting it.
How to Think About Rust Problems
Before fixing any issue, trace through the layers:
- Layer 3 — Domain (WHY): Business rules, performance constraints, deployment context. These constrain everything below.
- Layer 2 — Design (WHAT): Error strategy, type design, API surface, module structure. Check against SOLID and API Guidelines.
- Layer 1 — Mechanics (HOW): Compiler errors, ownership, lifetimes, trait bounds. Fix the immediate issue, but always trace UP.
When a compiler error appears, reframe it as a design question:
| Error | Don't Just Say | Ask Instead |
|---|---|---|
| E0382 (value moved) | "Clone it" | Who should own this data? |
| E0597 (doesn't live long enough) | "Add a lifetime" | Is the scope boundary correct? |
| E0277 (trait not satisfied) | "Add the bound" | Is this the right abstraction? |
| E0499 (two mutable borrows) | "Use RefCell" | Should this be two separate resources? |
| "future is not Send" | "Wrap in Arc" | Does this state need to cross threads? |
Ownership & Borrowing
→ Consult ownership reference for borrowing rules, Cow, smart pointers.
DO: Default to borrowing (&T). Move to owned only when the callee must store the value.
DO: Use &str and &[T] in function parameters — not &String or &Vec<T>.
DO: Use Cow<'_, str> when a function conditionally allocates.
DON'T: Clone to silence the borrow checker — restructure ownership instead.
DON'T: Over-annotate lifetimes — elision covers 95% of cases.
DON'T: Write &'a mut self on methods — borrows self for its entire lifetime.
Error Handling
→ Consult error-handling reference for thiserror/anyhow/snafu decision matrix.
DO: Use ? with .context() at every propagation point.
DO: Use thiserror for libraries, anyhow for applications.
DO: Use #[non_exhaustive] on public error enums.
DON'T: Use .unwrap() or .expect() in production paths — production services have been taken down by unhandled panics.
DON'T: Implement From for fallible conversions — use TryFrom.
DON'T: Both log AND propagate an error — pick one.
Type Design
→ Consult type-patterns reference for newtype, typestate, builder patterns.
DO: Parse, don't validate — convert raw inputs into types that carry their validity.
DO: Replace boolean parameters with enums — process(data, true, false) is unreadable.
DO: Use #[must_use] on functions returning values callers must handle.
DON'T: Use ..Default::default() — silently wrong when fields are added.
DON'T: Use catch-all _ in match on owned enums — swallows new variants.
Design Principles
→ Consult design-principles reference for SOLID, Microsoft M- rules, modern Rust table.*
DO: Apply Single Responsibility — one struct per concept, one domain per module.
DO: Depend on traits, not concrete types (Dependency Inversion).
DO: Use #[expect(lint)] instead of #[allow(lint)] (warns when stale, Rust 1.81+).
DON'T: Use weasel word names — BookingService, DataManager. Name types after what they ARE.
DON'T: Expose Arc, Rc, Box in public API signatures — hide implementation details.
Traits & API Surface
→ Consult traits reference for generics vs dyn, standard traits, sealed patterns.
DO: Implement standard traits eagerly (Debug, Clone, PartialEq, Hash, Default).
DO: Default to generics. Use dyn Trait only for genuine runtime polymorphism.
DON'T: Violate Hash/Eq consistency — the most dangerous silent bug in Rust's stdlib.
Async
→ Consult async reference for blocking taxonomy, cancellation safety, JoinSet.
DO: Keep business logic synchronous. Use async only at I/O boundaries.
DO: Use CancellationToken for graceful shutdown — not task.abort().
DON'T: Block async threads for more than 10-100 microseconds between .await points.
DON'T: Hold a MutexGuard across .await — drop it first.
DON'T: Make every function async "just in case" — async infects signatures upward.
Concurrency
→ Consult concurrency reference for decision tree, actor pattern, channels.
DO: Ask first: "Do I actually need concurrency?" If no measured bottleneck, stay sequential.
DO: Prefer channels and actors over shared mutable state.
DO: Use bounded channels in production for backpressure.
DON'T: Default to Arc<Mutex<T>> — it's expensive and often unnecessary.
DON'T: Use async for CPU-bound work — use Rayon or spawn_blocking.
Unsafe
→ Consult unsafe reference for SAFETY comments, Miri, UB patterns.
DO: Every unsafe block needs a // SAFETY: comment explaining the invariant.
DO: Run cargo +nightly miri test on code with unsafe.
DO: Deny unsafe at crate level (unsafe_code = "deny") with surgical allows.
DON'T: Use unsafe when safe alternatives exist — all memory-safety CVEs in Rust trace to unsafe code.
Performance
→ Consult performance reference for build config, allocation patterns, benchmarking.
DO: Profile before optimizing — cargo flamegraph, DHAT, samply.
DO: Use overflow-checks = true in release profiles (CVE-2018-1000810).
DO: Use strict_add / strict_sub (1.91) instead of checked_add().unwrap().
DON'T: Optimize without a measured bottleneck.
Testing & Documentation
→ Consult testing and documentation references for test frameworks, property testing, and API docs guidelines.
Modules, Macros & Serde
→ Consult modules-cargo, macros, and serde references for workspace setup, macro decision flowchart, and serialization patterns.
Anti-Patterns
→ Consult anti-patterns reference for the full severity-labeled catalog.
The Rust AI Slop Test
→ Consult ai-slop reference for the complete fingerprint catalog.
Critical quality check: If a senior Rust engineer reviewed this code, would they immediately suspect AI generated it? If yes, that's the problem.
The most common AI tells in Rust:
.clone()everywhere to silence the borrow checkerArc<Mutex<T>>as default concurrency for everything.unwrap()on everyResultandOption- Traits with exactly one implementation
- Over-annotated lifetimes where elision works
asyncon functions that never.await- Verbose comments explaining WHAT, never WHY
- Generic variable names (
data,result,iteminstead of domain names) - Premature generalization with unused generic parameters
- No refactoring — duplicated blocks with minor variations
#[allow(...)]to suppress warnings instead of fixing root cause
Thinking Prompts
Before suggesting any fix, work through:
- What bug does this prevent? If you cannot name a concrete bug, the fix may not be worth the complexity.
- What would happen in production? Think in terms of incidents, not style.
- Is the type system doing enough work? Every runtime
assert!is a type waiting to be born.
Severity Levels
Label every finding:
- blocking — Soundness bug, UB, data race, guaranteed panic. Must fix.
- important — Wrong error handling, performance cliff, design pain. Should fix.
- nit — Style, naming, minor idiom. Fix if convenient.
- suggestion — Alternative worth considering. No action required.
- praise — Highlight well-written code. Reinforce good patterns.
Output Format
Group findings by file. For each finding:
- File path and line number
- Severity label
- Rule name
- WHY it matters — the concrete consequence
- Before/after code block when the fix is non-obvious
Skip files with no findings. End with a prioritized summary.
CRITICAL: Be direct — vague feedback wastes time. Be specific — "line 42 of parser.rs" not "some functions". Say what's wrong AND why it matters. Prioritize ruthlessly — if everything is important, nothing is.