# Rust Best Practices

> This skill should be used when implementing, fixing, writing, reviewing, or discussing Rust code quality, API design, safety, or idioms. It applies the Microsoft Pragmatic Rust Guidelines and Rust API Guidelines checklists.

- Skill: `lklimek/rust-best-practices` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add lklimek/rust-best-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lklimek/rust-best-practices/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: lklimek (https://skillmd.com/u/lklimek)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lklimek/rust-best-practices

---


# Rust Best Practices

Two authoritative sources of Rust best practices, presented as checklists for quick reference.

## How to Use

1. **Writing/review**: scan the relevant checklist sections below
2. **Detailed guidance**: read the reference file linked in each section header
3. **API design**: Rust API Guidelines checklist (C-prefixed); **production systems**: Microsoft checklist (M-prefixed)

### Sources

- Microsoft Pragmatic Rust Guidelines: https://microsoft.github.io/rust-guidelines/
  - AI-friendly condensed version: https://microsoft.github.io/rust-guidelines/agents/all.txt
- Rust API Guidelines: https://rust-lang.github.io/api-guidelines/

## Technical Standards & Patterns

- **Rust Edition**: latest stable (2021 or newer)
- **Code Style**: rustfmt defaults; **Linting**: clippy with `deny(warnings)` in CI
- **Documentation**: one-line `///` comment for every public item; expand only when non-obvious
- **Error Handling**: `thiserror` typed error enums (see below)
- **Dependencies**: minimal, prefer std
- **Async**: tokio; **Serialization**: serde derive; **CLI**: clap; **Logging**: `tracing` crate (see coding-best-practices § Logging Levels)
- **Testing**: cargo test with doc tests; proptest for property-based testing
- **Benchmarking**: criterion

## Error Handling

**Preferred crate: `thiserror`** with typed error enums. Always define explicit error enums — never erase error types into opaque wrappers.

### Design Principles

- **`Display` = user-friendly, `Debug` = technical**: `Display` (auto-generated by `#[error(...)]`) is the message shown to users or logged at info level; `Debug` preserves the full error chain for diagnostics.
- **Granular variants over generic strings**: add a dedicated variant with `#[source]` rather than `format!`-ing into a catch-all `Generic(String)` — preserves the error chain, enables structural matching, keeps `Display`/`Debug` separation clean.
- **`#[from]` for automatic conversion**: wire upstream errors as `#[from]` variants so `?` works without manual mapping. `Box<LargeError>` for large upstream types keeps the enum size reasonable.
- **Omit `#[source]` when the upstream error is useless** (e.g. a channel `SendError`).

### Pattern

```rust
use thiserror::Error;

#[derive(Debug, Error)]
pub enum MyError {
    #[error("Failed to load configuration")]
    Config {
        #[source]
        source: std::io::Error,
    },

    #[error("Invalid input: expected {expected}, got {actual}")]
    Validation { expected: String, actual: String },

    #[error("Database operation failed")]
    Database(#[from] rusqlite::Error),

    // Box large upstream errors
    #[error("SDK call failed")]
    Sdk(#[from] Box<SdkError>),
}
```

### Anti-Patterns

- `Result<T, String>` — loses error chain, prevents matching, no `#[source]`
- `.map_err(|e| format!("{e}"))` — destroys the original error; use a typed variant
- Catch-all `Generic(String)` as default — last resort for one-off strings with no upstream error
- `unwrap()` / `expect()` in non-test production code — handle or propagate with `?`
- Exposing raw error strings to end users — `Display` should be actionable and jargon-free

## Common Pitfalls

- Don't clone unnecessarily — use references
- Don't use unwrap() in production code — handle errors properly
- Don't use unsafe without extensive justification and safety comments
- Don't fight the borrow checker — redesign if struggling
- Don't ignore clippy warnings — fix or explicitly allow with reasoning
- Don't use Arc<Mutex<T>> when RefCell or channels would work
- Don't rely on `debug_assert!`/`debug_assert_eq!`/`cfg(debug_assertions)` for correctness or safety invariants — compiled out in release builds. Validate at runtime and **return a typed error**. `panic!`/`assert!`/`.unwrap()` are not an acceptable default — reserve for genuinely unrecoverable invariant violations.

## Code Quality Tools

- **Compilation + Linting**: `cargo-cached.sh clippy -p <your crates> --all-targets -- -D warnings` while iterating (never `cargo check` — clippy is a strict superset); workspace-wide `--all-features` clippy is the merge gate's job, run once per merged tree. (Wrapper's absolute path is in the SessionStart Rust build environment context — see Build Optimization.)
- **Formatting**: `cargo fmt`
- **Testing**: `cargo-cached.sh test -p <your crates>` while iterating; `cargo test --all-features --workspace` (via the wrapper) only at the merge gate.
- **Security**: `cargo audit`
- **Coverage**: cargo-tarpaulin or cargo-llvm-cov
- **Documentation**: `cargo doc --no-deps --open`
- **LSP Diagnostics**: rust-analyzer (see LSP Integration below)

## Build Optimization

Rust builds are expensive. `cargo build`, `cargo clippy`, and `cargo test` all compile the code — never chain them or run one as a pre-check for another.

- **Use LSP as primary feedback loop** — rust-analyzer catches errors without a rebuild
- **Defer builds to QA phase** — don't run `cargo test`/`cargo clippy`/`cargo fmt` after every edit
- **Never use `cargo check`** — `cargo clippy` is a strict superset (compilation + lints)
- **Never pre-compile** — `cargo check && cargo test` or `cargo clippy && cargo build` wastes a full compile cycle; run the target command directly
- **Capture output with `tee`** — see `coding-best-practices` § Build & Test Output Capture
- **Always go through the `cargo-cached.sh` wrapper** for test/clippy/nextest (absolute path announced in the SessionStart Rust build environment context — the plugin-relative `scripts/cargo-cached.sh` only resolves inside the plugin itself) — identical command + identical tree replays the recorded log instantly, across all agents and worktrees. The PreToolUse hook enforces this for test/clippy/nextest; plain `build` may route through it for dedup but is not hook-enforced (a build's output is an artifact, not a replayable verdict).
- **Don't override `CARGO_TARGET_DIR`/`--target-dir` manually — isolation is automatic**: any invocation through `cargo-cached.sh` auto-derives a per-checkout, path-keyed target dir, so concurrent same-HEAD worktrees/clones can't collide. `CLAUDIUS_TARGET_PREFIX` roots the hashed dirs elsewhere; an explicit `CARGO_TARGET_DIR` via `CLAUDIUS_ISOLATE_TARGET=1` wins (the manual escape hatch for edge cases — see `grand-admiral` § Worktree Isolation, not routine); unset/empty prefix keeps the canonical default. The hook denies ad-hoc overrides. A raw `cargo build` NOT routed through the wrapper uses the machine's shared `~/.cargo/config.toml` dir and sccache. Caveat: a bare `cargo metadata` outside the wrapper reports the shared dir, not the isolated one — don't use it to locate a wrapper-built artifact.
- **Prefer `cargo nextest run` for test-heavy iteration when installed** (check `command -v cargo-nextest`; SessionStart context states availability) — nextest skips doctests, so the merge gate still needs a `cargo test` (or nextest + a separate `--doc` pass).

## Code Review Checklist

- Code readability and self-documentation
- DRY compliance: duplicated logic, copy-paste patterns, missing abstractions
- Naming clarity: variables, functions, types, modules
- Error handling completeness (no silent unwrap in non-test code)
- Performance: unnecessary allocations, clone overhead, iterator vs collect patterns
- Test quality: meaningful assertions, edge cases, error paths covered
- Magic numbers replaced with named constants
- Code brevity: flag code that can be expressed in fewer lines without losing clarity

Use `RUST-NNN` prefix for all findings.

---

## Microsoft Pragmatic Rust Guidelines Checklist

For detailed descriptions of any M-prefixed item, read `references/microsoft-guidelines.md`.

### Universal
- [ ] **M-PRIOR-ART** — Before implementing custom logic, search crates.io and docs.rs for existing well-maintained crates; prefer established crates over custom implementations
- [ ] **M-UPSTREAM-GUIDELINES** — Follow the upstream Rust API Guidelines, Style Guide, and Design Patterns
- [ ] **M-STATIC-VERIFICATION** — Use clippy, rustfmt, cargo-audit, cargo-hack, cargo-udeps, miri
- [ ] **M-LINT-OVERRIDE-EXPECT** — Use `#[expect]` instead of `#[allow]` for lint overrides
- [ ] **M-PUBLIC-DEBUG** — All public types implement `Debug`
- [ ] **M-PUBLIC-DISPLAY** — Public types meant to be read implement `Display`
- [ ] **M-SMALLER-CRATES** — If in doubt, split the crate into smaller ones
- [ ] **M-CONCISE-NAMES** — Names are free of weasel words (Service, Manager, Factory)
- [ ] **M-REGULAR-FN** — Prefer regular functions over associated functions for non-receiver logic
- [ ] **M-PANIC-IS-STOP** — Panic means "stop the program", never for error communication
- [ ] **M-PANIC-ON-BUG** — Detected programming bugs are panics, not errors
- [ ] **M-DOCUMENTED-MAGIC** — All magic values and behaviors are documented
- [ ] **M-LOG-STRUCTURED** — Use structured logging with message templates

### Library / Interoperability
- [ ] **M-TYPES-SEND** — Types are `Send` for Tokio/runtime compatibility
- [ ] **M-ESCAPE-HATCHES** — Native types provide `unsafe` escape hatches for FFI
- [ ] **M-DONT-LEAK-TYPES** — Don't leak external crate types in public APIs

### Library / UX
- [ ] **M-SIMPLE-ABSTRACTIONS** — Abstractions don't visibly nest (no `Foo<Bar<Baz>>`)
- [ ] **M-AVOID-WRAPPERS** — Avoid smart pointers and wrappers in public APIs
- [ ] **M-DI-HIERARCHY** — Prefer types > generics > dyn traits for dependency injection
- [ ] **M-ERRORS-CANONICAL-STRUCTS** — Errors are canonical structs with backtrace and cause
- [ ] **M-INIT-BUILDER** — Complex type construction uses builders (4+ permutations)
- [ ] **M-INIT-CASCADED** — Complex initialization hierarchies use semantic grouping
- [ ] **M-SERVICES-CLONE** — Service types implement `Clone` via `Arc<Inner>`
- [ ] **M-IMPL-ASREF** — Accept `impl AsRef<>` where feasible (str, Path, [u8])
- [ ] **M-IMPL-RANGEBOUNDS** — Accept `impl RangeBounds<>` where feasible
- [ ] **M-IMPL-IO** — Accept `impl Read`/`impl Write` where feasible (Sans IO)
- [ ] **M-ESSENTIAL-FN-INHERENT** — Essential functionality is inherent, not trait-only

### Library / Resilience
- [ ] **M-MOCKABLE-SYSCALLS** — I/O and system calls are mockable
- [ ] **M-TEST-UTIL** — Test utilities are feature-gated behind `test-util`
- [ ] **M-STRONG-TYPES** — Use the strongest type available (PathBuf over String)
- [ ] **M-NO-GLOB-REEXPORTS** — Don't glob re-export items
- [ ] **M-AVOID-STATICS** — Avoid statics when consistency matters for correctness

### Library / Building
- [ ] **M-OOBE** — Libraries work out of the box on all Tier 1 platforms
- [ ] **M-SYS-CRATES** — Native `-sys` crates compile without external dependencies
- [ ] **M-FEATURES-ADDITIVE** — Features are additive; any combination works

### Applications
- [ ] **M-MIMALLOC-APP** — Use mimalloc as global allocator for applications
- [ ] **M-APP-ERROR** — Applications use `thiserror` with typed error enums

### FFI
- [ ] **M-ISOLATE-DLL-STATE** — Isolate DLL state between FFI libraries; share only `#[repr(C)]` data

### Safety
- [ ] **M-UNSAFE** — Unsafe needs a documented reason and should be avoided
- [ ] **M-UNSAFE-IMPLIES-UB** — Mark functions `unsafe` only when misuse causes UB
- [ ] **M-UNSOUND** — All code must be sound; no exceptions

### Performance
- [ ] **M-THROUGHPUT** — Optimize for throughput (items per CPU cycle), avoid empty cycles
- [ ] **M-HOTPATH** — Identify, profile, and optimize the hot path early
- [ ] **M-YIELD-POINTS** — Long-running tasks have yield points (10-100us between yields)

### Documentation
- [ ] **M-NO-TOMBSTONES** — Never add comments explaining removed code; git history is the record
- [ ] **M-FIRST-DOC-SENTENCE** — First doc sentence is one line, ~15 words
- [ ] **M-MODULE-DOCS** — Non-trivial public modules have `//!` documentation
- [ ] **M-CANONICAL-DOCS** — Complex APIs have canonical doc sections (Examples, Errors, Panics, Safety)
- [ ] **M-DOC-INLINE** — Mark `pub use` items with `#[doc(inline)]`

### AI
- [ ] **M-DESIGN-FOR-AI** — Design with AI use in mind (strong types, thorough docs, testable APIs)

---

## Rust API Guidelines Checklist

For detailed descriptions of any C-prefixed item, read `references/api-guidelines.md`.

### Naming (C-*)
- [ ] **C-CASE** — Casing conforms to RFC 430 (CamelCase types, snake_case functions)
- [ ] **C-CONV** — Conversions follow `as_` (free, borrowed), `to_` (expensive), `into_` (owned)
- [ ] **C-GETTER** — Getters omit `get_` prefix; use field name directly
- [ ] **C-ITER** — Collection iterators use `iter`, `iter_mut`, `into_iter`
- [ ] **C-ITER-TY** — Iterator type names match producing methods
- [ ] **C-FEATURE** — Feature names are free of placeholder words (no `use-`, `with-`, `no-`)
- [ ] **C-WORD-ORDER** — Names use consistent word order (verb-object-error)

### Interoperability (C-*)
- [ ] **C-COMMON-TRAITS** — Types eagerly implement Copy, Clone, Eq, PartialEq, Ord, Hash, Debug, Display, Default
- [ ] **C-CONV-TRAITS** — Conversions use `From`, `TryFrom`, `AsRef`, `AsMut`
- [ ] **C-COLLECT** — Collections implement `FromIterator` and `Extend`
- [ ] **C-SERDE** — Data structures implement Serde (optionally feature-gated)
- [ ] **C-SEND-SYNC** — Types are `Send` and `Sync` where possible
- [ ] **C-GOOD-ERR** — Error types implement `Error + Send + Sync` with meaningful `Display`
- [ ] **C-NUM-FMT** — Binary number types provide Hex, Octal, Binary formatting
- [ ] **C-RW-VALUE** — Generic reader/writer functions take `R: Read` and `W: Write` by value

### Macros (C-*)
- [ ] **C-EVOCATIVE** — Input syntax mirrors the output it produces
- [ ] **C-MACRO-ATTR** — Item macros compose well with attributes
- [ ] **C-ANYWHERE** — Item macros work anywhere items are allowed
- [ ] **C-MACRO-VIS** — Item macros support visibility specifiers
- [ ] **C-MACRO-TY** — Type fragments are flexible (primitives, paths, generics)

### Documentation (C-*)
- [ ] **C-CRATE-DOC** — Crate-level docs are thorough and include examples
- [ ] **C-EXAMPLE** — Non-trivial public items have a rustdoc example
- [ ] **C-QUESTION-MARK** — Examples use `?`, not `try!`, not `unwrap`
- [ ] **C-FAILURE** — Function docs include Error, Panic, and Safety sections
- [ ] **C-LINK** — Prose contains hyperlinks to relevant things
- [ ] **C-METADATA** — Cargo.toml includes all common metadata
- [ ] **C-RELNOTES** — Release notes document all significant changes
- [ ] **C-HIDDEN** — Rustdoc does not show unhelpful implementation details

### Predictability (C-*)
- [ ] **C-SMART-PTR** — Smart pointers do not add inherent methods
- [ ] **C-CONV-SPECIFIC** — Conversions live on the most specific type
- [ ] **C-METHOD** — Functions with a clear receiver are methods
- [ ] **C-NO-OUT** — Functions do not take out-parameters
- [ ] **C-OVERLOAD** — Operator overloads are unsurprising
- [ ] **C-DEREF** — Only smart pointers implement `Deref`/`DerefMut`
- [ ] **C-CTOR** — Constructors are static, inherent methods (`new()`)

### Flexibility (C-*)
- [ ] **C-INTERMEDIATE** — Functions expose intermediate results to avoid duplicate work
- [ ] **C-CALLER-CONTROL** — Caller decides where to copy and place data
- [ ] **C-GENERIC** — Functions minimize assumptions using generics
- [ ] **C-OBJECT** — Traits are object-safe if useful as trait objects

### Type Safety (C-*)
- [ ] **C-NEWTYPE** — Newtypes provide static distinctions (Miles vs Kilometers)
- [ ] **C-CUSTOM-TYPE** — Arguments convey meaning through types, not `bool` or `Option`
- [ ] **C-BITFLAG** — Flag sets use `bitflags`, not enums
- [ ] **C-BUILDER** — Builders enable construction of complex values

### Dependability (C-*)
- [ ] **C-VALIDATE** — Functions validate their arguments (prefer static > dynamic)
- [ ] **C-DTOR-FAIL** — Destructors never fail
- [ ] **C-DTOR-BLOCK** — Destructors that may block have alternatives

### Debuggability (C-*)
- [ ] **C-DEBUG** — All public types implement `Debug`
- [ ] **C-DEBUG-NONEMPTY** — `Debug` representation is never empty

### Future Proofing (C-*)
- [ ] **C-SEALED** — Sealed traits protect against downstream implementations
- [ ] **C-STRUCT-PRIVATE** — Structs have private fields with accessor methods
- [ ] **C-NEWTYPE-HIDE** — Newtypes encapsulate implementation details
- [ ] **C-STRUCT-BOUNDS** — Data structures do not duplicate derived trait bounds

### Necessities (C-*)
- [ ] **C-STABLE** — Public dependencies of a stable crate are stable
- [ ] **C-PERMISSIVE** — Crate and dependencies have a permissive license (MIT/Apache-2.0)

---

## rust-analyzer LSP Integration

The `rust-analyzer-lsp` plugin (from `claude-plugins-official`) provides LSP code intelligence for Rust files. When available, use it for:

- **Diagnostics**: compilation errors, warnings, type mismatches without running `cargo build`
- **Navigation**: definitions, references, type hierarchies
- **Type inspection**: hover for inferred types and documentation

LSP diagnostics are a fast feedback complement, not a replacement — comprehensive checks still require `cargo clippy` and `cargo test`.

