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.
DO: Treat Arc::clone(&handle) of service handles, DB pools, and channels as idiomatic — that is what M-SERVICES-CLONE is for.
DON'T: Clone owned heap data (Vec, String, large structs) 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 (v2) for libraries, anyhow (v2) for applications.
DO: Use #[non_exhaustive] on public error enums.
DO: Use .expect("invariant X holds because Y") to assert what the type system cannot express — invariant assertion is fine; lazy error handling is not.
DON'T: Use .unwrap() or .expect() on Results from outside the program (parse, IO, env, deserialize, network) — this is what took down Cloudflare on Nov 18, 2025: a hard-coded 200-feature limit hit unexpected input, .unwrap() on the Err panicked in fl2_worker_thread, 5xx globally for hours.
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, atomic orderings.
DO: Ask first: "Do I actually need concurrency?" If no measured bottleneck, stay sequential.
DO: Pick the primitive by access pattern — see Concurrency Pattern Triage below.
DO: Use bounded channels in production for backpressure.
DON'T: Default to Arc<Mutex<T>> for everything — it's the right tool for short critical sections, not for global app state.
DON'T: Use async for CPU-bound work — use Rayon or spawn_blocking.
When to reach for which concurrency primitive
Pick by access pattern, not reflex. Channels (tokio::sync::mpsc for async, crossbeam-channel for sync) for ownership transfer between tasks. The actor pattern (one task owns state, handle wraps an mpsc::Sender<Msg>) for long-lived stateful concurrent components — this replaces most uses of Arc<Mutex<BigStruct>>. Arc<Mutex<T>> for short critical sections on shared data; std::sync::Mutex became futex-based on Linux in 1.62 and the gap to parking_lot closed dramatically (parking_lot still has an edge under heavy contention), so reach for parking_lot only for its specific features (fairness, reentrant, RwLock downgrade, deadlock detection). Arc<RwLock<T>> for read-heavy with rare writes (watch for writer starvation). ArcSwap<T> for snapshot reload (lock-free reads, atomic swap). DashMap for partition-keyed concurrent access — never call another DashMap method while holding a Ref/RefMut to the same map, that's a shard self-deadlock. Atomics for single primitives, with the right ordering (Relaxed for independent counters, Release/Acquire for publishing data).
The anti-pattern is Arc<Mutex<WholeAppState>> as a god-object. Split by concern, let each piece pick its own primitive.
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