- name:
- rust-best-practices
- description:
- Rust best practices checklists (Microsoft Pragmatic + Rust API Guidelines). Use when implementing, fixing, writing, reviewing, or discussing Rust code quality, API design, safety, or idioms.
- allowed-tools:
- Read, WebFetch
Rust Best Practices
Two authoritative sources of Rust best practices, presented as checklists for quick reference.
When you need detailed guidance on a specific item, read the corresponding reference file.
How to Use
- During code writing/review: Scan the relevant checklist sections below
- For detailed guidance: Read the reference file linked in each section header
- For API design: Focus on the Rust API Guidelines checklist (C-prefixed items)
- For production systems: Focus on the Microsoft Guidelines checklist (M-prefixed items)
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
- Rust Edition: Latest stable (2021 or newer)
- Code Style: rustfmt with default settings
- Linting: clippy with
deny(warnings) in CI
- Testing: cargo test with doc tests
- Documentation: One-line
/// comment for every public item; expand only when non-obvious
- Error Handling:
thiserror for typed errors; see Error Handling section below
- Dependencies: Minimal, prefer std when possible
- Async: tokio for async runtime when needed
Common Patterns
- Error Handling:
thiserror with typed error enums
- Async: tokio with async/await
- Serialization: serde with derive macros
- CLI: clap for argument parsing
- Logging:
tracing crate (see coding-best-practices § Logging Levels)
- Testing: cargo test, proptest for property-based testing
- Benchmarking: criterion for performance benchmarks
Error Handling
Preferred crate: thiserror with typed error enums. Always define explicit error enums — avoid erasing error types into opaque wrappers.
Design Principles
Display = user-friendly, Debug = technical: Display impl (auto-generated by #[error(...)]) produces 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 enum variant with
#[source] rather than Format!-ing into a catch-all Generic(String). Granular variants preserve the error chain, enable structural matching, and keep Display/Debug separation clean.
#[from] for automatic conversion: Wire upstream errors as #[from] variants so ? works without manual mapping. Use Box<LargeError> for large upstream types to keep the enum size reasonable.
- Omit
#[source] when upstream error is useless: If the wrapped error carries no meaningful diagnostic info (e.g., a channel SendError), skip the #[source] field.
Pattern
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 instead
- 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
Code Quality Tools
- Compilation + Linting:
cargo clippy --all-features --all-targets -- -D warnings (replaces cargo check — never use cargo check)
- Formatting:
cargo fmt
- Testing:
cargo test --all-features --workspace
- Security:
cargo audit
- Coverage: cargo-tarpaulin or cargo-llvm-cov
- Documentation:
cargo doc --no-deps --open
- LSP Diagnostics: rust-analyzer (see LSP Integration section 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 full 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
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-based code intelligence for Rust files. When available, use it to:
- Get diagnostics: Check for compilation errors, warnings, and type mismatches without running
cargo build
- Navigate code: Go to definitions, find references, and understand type hierarchies
- Inspect types: Hover over expressions to see inferred types and documentation
Use LSP diagnostics as a fast feedback loop during development and code review. For comprehensive checks, still run cargo clippy and cargo test — LSP diagnostics are a complement, not a replacement.