Rust Best Practices
Comprehensive guide for writing high-quality, idiomatic, and highly optimized Rust code. Contains 179 rules across 14 categories, prioritized by impact to guide LLMs in code generation and refactoring.
When to Apply
Reference these guidelines when:
Writing new Rust functions, structs, or modules
Implementing error handling or async code
Designing public APIs for libraries
Reviewing code for ownership/borrowing issues
Optimizing memory usage or reducing allocations
Tuning performance for hot paths
Refactoring existing Rust code
Rule Categories by Priority
Priority Category Impact Prefix Rules
1 Ownership & Borrowing CRITICAL own- 12
2 Error Handling CRITICAL err- 12
3 Memory Optimization CRITICAL mem- 15
4 API Design HIGH api- 15
5 Async/Await HIGH async- 15
6 Compiler Optimization HIGH opt- 12
7 Naming Conventions MEDIUM name- 16
8 Type Safety MEDIUM type- 10
9 Testing MEDIUM test- 13
10 Documentation MEDIUM doc- 11
11 Performance Patterns MEDIUM perf- 11
12 Project Structure LOW proj- 11
13 Clippy & Linting LOW lint- 11
14 Anti-patterns REFERENCE anti- 15
Quick Reference
1. Ownership & Borrowing (CRITICAL)
own-borrow-over-clone - Prefer &T borrowing over .clone()
own-slice-over-vec - Accept &[T] not &Vec, &str not &String
own-cow-conditional - Use Cow<'a, T> for conditional ownership
own-arc-shared - Use Arc for thread-safe shared ownership
own-rc-single-thread - Use Rc for single-threaded sharing
own-refcell-interior - Use RefCell for interior mutability (single-thread)
own-mutex-interior - Use Mutex for interior mutability (multi-thread)
own-rwlock-readers - Use RwLock when reads dominate writes
own-copy-small - Derive Copy for small, trivial types
own-clone-explicit - Make Clone explicit, avoid implicit copies
own-move-large - Move large data instead of cloning
own-lifetime-elision - Rely on lifetime elision when possible
2. Error Handling (CRITICAL)
err-thiserror-lib - Use thiserror for library error types
err-anyhow-app - Use anyhow for application error handling
err-result-over-panic - Return Result, don't panic on expected errors
err-context-chain - Add context with .context() or .with_context()
err-no-unwrap-prod - Never use .unwrap() in production code
err-expect-bugs-only - Use .expect() only for programming errors
err-question-mark - Use ? operator for clean propagation
err-from-impl - Use #[from] for automatic error conversion
err-source-chain - Use #[source] to chain underlying errors
err-lowercase-msg - Error messages: lowercase, no trailing punctuation
err-doc-errors - Document errors with # Errors section
err-custom-type - Create custom error types, not Box
3. Memory Optimization (CRITICAL)
mem-with-capacity - Use with_capacity() when size is known
mem-smallvec - Use SmallVec for usually-small collections
mem-arrayvec - Use ArrayVec for bounded-size collections
mem-box-large-variant - Box large enum variants to reduce type size
mem-boxed-slice - Use Box<[T]> instead of Vec when fixed
mem-thinvec - Use ThinVec for often-empty vectors
mem-clone-from - Use clone_from() to reuse allocations
mem-reuse-collections - Reuse collections with clear() in loops
mem-avoid-format - Avoid format!() when string literals work
mem-write-over-format - Use write!() instead of format!()
mem-arena-allocator - Use arena allocators for batch allocations
mem-zero-copy - Use zero-copy patterns with slices and Bytes
mem-compact-string - Use CompactString for small string optimization
mem-smaller-integers - Use smallest integer type that fits
mem-assert-type-size - Assert hot type sizes to prevent regressions
4. API Design (HIGH)
api-builder-pattern - Use Builder pattern for complex construction
api-builder-must-use - Add #[must_use] to builder types
api-newtype-safety - Use newtypes for type-safe distinctions
api-typestate - Use typestate for compile-time state machines
api-sealed-trait - Seal traits to prevent external implementations
api-extension-trait - Use extension traits to add methods to foreign types
api-parse-dont-validate - Parse into validated types at boundaries
api-impl-into - Accept impl Into for flexible string inputs
api-impl-asref - Accept impl AsRef for borrowed inputs
api-must-use - Add #[must_use] to Result returning functions
api-non-exhaustive - Use #[non_exhaustive] for future-proof enums/structs
api-from-not-into - Implement From, not Into (auto-derived)
api-default-impl - Implement Default for sensible defaults
api-common-traits - Implement Debug, Clone, PartialEq eagerly
api-serde-optional - Gate Serialize/Deserialize behind feature flag
5. Async/Await (HIGH)
async-tokio-runtime - Use Tokio for production async runtime
async-no-lock-await - Never hold Mutex/RwLock across .await
async-spawn-blocking - Use spawn_blocking for CPU-intensive work
async-tokio-fs - Use tokio::fs not std::fs in async code
async-cancellation-token - Use CancellationToken for graceful shutdown
async-join-parallel - Use tokio::join! for parallel operations
async-try-join - Use tokio::try_join! for fallible parallel ops
async-select-racing - Use tokio::select! for racing/timeouts
async-bounded-channel - Use bounded channels for backpressure
async-mpsc-queue - Use mpsc for work queues
async-broadcast-pubsub - Use broadcast for pub/sub patterns
async-watch-latest - Use watch for latest-value sharing
async-oneshot-response - Use oneshot for request/response
async-joinset-structured - Use JoinSet for dynamic task groups
async-clone-before-await - Clone data before await, release locks
6. Compiler Optimization (HIGH)
opt-inline-small - Use #[inline] for small hot functions
opt-inline-always-rare - Use #[inline(always)] sparingly
opt-inline-never-cold - Use #[inline(never)] for cold paths
opt-cold-unlikely - Use #[cold] for error/unlikely paths
opt-likely-hint - Use likely()/unlikely() for branch hints
opt-lto-release - Enable LTO in release builds
opt-codegen-units - Use codegen-units = 1 for max optimization
opt-pgo-profile - Use PGO for production builds
opt-target-cpu - Set target-cpu=native for local builds
opt-bounds-check - Use iterators to avoid bounds checks
opt-simd-portable - Use portable SIMD for data-parallel ops
opt-cache-friendly - Design cache-friendly data layouts (SoA)
7. Naming Conventions (MEDIUM)
name-types-camel - Use UpperCamelCase for types, traits, enums
name-variants-camel - Use UpperCamelCase for enum variants
name-funcs-snake - Use snake_case for functions, methods, modules
name-consts-screaming - Use SCREAMING_SNAKE_CASE for constants/statics
name-lifetime-short - Use short lowercase lifetimes: 'a, 'de, 'src
name-type-param-single - Use single uppercase for type params: T, E, K, V
name-as-free - as_ prefix: free reference conversion
name-to-expensive - to_ prefix: expensive conversion
name-into-ownership - into_ prefix: ownership transfer
name-no-get-prefix - No get_ prefix for simple getters
name-is-has-bool - Use is_, has_, can_ for boolean methods
name-iter-convention - Use iter/iter_mut/into_iter for iterators
name-iter-method - Name iterator methods consistently
name-iter-type-match - Iterator type names match method
name-acronym-word - Treat acronyms as words: Uuid not UUID
name-crate-no-rs - Crate names: no -rs suffix
8. Type Safety (MEDIUM)
type-newtype-ids - Wrap IDs in newtypes: UserId(u64)
type-newtype-validated - Newtypes for validated data: Email, Url
type-enum-states - Use enums for mutually exclusive states
type-option-nullable - Use Option for nullable values
type-result-fallible - Use Result for fallible operations
type-phantom-marker - Use PhantomData for type-level markers
type-never-diverge - Use ! type for functions that never return
type-generic-bounds - Add trait bounds only where needed
type-no-stringly - Avoid stringly-typed APIs, use enums/newtypes
type-repr-transparent - Use #[repr(transparent)] for FFI newtypes
9. Testing (MEDIUM)
test-cfg-test-module - Use #[cfg(test)] mod tests { }
test-use-super - Use use super::*; in test modules
test-integration-dir - Put integration tests in tests/ directory
test-descriptive-names - Use descriptive test names
test-arrange-act-assert - Structure tests as arrange/act/assert
test-proptest-properties - Use proptest for property-based testing
test-mockall-mocking - Use mockall for trait mocking
test-mock-traits - Use traits for dependencies to enable mocking
test-fixture-raii - Use RAII pattern (Drop) for test cleanup
test-tokio-async - Use #[tokio::test] for async tests
test-should-panic - Use #[should_panic] for panic tests
test-criterion-bench - Use criterion for benchmarking
test-doctest-examples - Keep doc examples as executable tests
10. Documentation (MEDIUM)
doc-all-public - Document all public items with ///
doc-module-inner - Use //! for module-level documentation
doc-examples-section - Include # Examples with runnable code
doc-errors-section - Include # Errors for fallible functions
doc-panics-section - Include # Panics for panicking functions
doc-safety-section - Include # Safety for unsafe functions
doc-question-mark - Use ? in examples, not .unwrap()
doc-hidden-setup - Use # prefix to hide example setup code
doc-intra-links - Use intra-doc links: [Vec]
doc-link-types - Link related types and functions in docs
doc-cargo-metadata - Fill Cargo.toml metadata
11. Performance Patterns (MEDIUM)
perf-iter-over-index - Prefer iterators over manual indexing
perf-iter-lazy - Keep iterators lazy, collect() only when needed
perf-collect-once - Don't collect() intermediate iterators
perf-entry-api - Use entry() API for map insert-or-update
perf-drain-reuse - Use drain() to reuse allocations
perf-extend-batch - Use extend() for batch insertions
perf-chain-avoid - Avoid chain() in hot loops
perf-collect-into - Use collect_into() for reusing containers
perf-black-box-bench - Use black_box() in benchmarks
perf-release-profile - Optimize release profile settings
perf-profile-first - Profile before optimizing
12. Project Structure (LOW)
proj-lib-main-split - Keep main.rs minimal, logic in lib.rs
proj-mod-by-feature - Organize modules by feature, not type
proj-flat-small - Keep small projects flat
proj-mod-rs-dir - Use mod.rs for multi-file modules
proj-pub-crate-internal - Use pub(crate) for internal APIs
proj-pub-super-parent - Use pub(super) for parent-only visibility
proj-pub-use-reexport - Use pub use for clean public API
proj-prelude-module - Create prelude module for common imports
proj-bin-dir - Put multiple binaries in src/bin/
proj-workspace-large - Use workspaces for large projects
proj-workspace-deps - Use workspace dependency inheritance
13. Clippy & Linting (LOW)
lint-deny-correctness - #![deny(clippy::correctness)]
lint-warn-suspicious - #![warn(clippy::suspicious)]
lint-warn-style - #![warn(clippy::style)]
lint-warn-complexity - #![warn(clippy::complexity)]
lint-warn-perf - #![warn(clippy::perf)]
lint-pedantic-selective - Enable clippy::pedantic selectively
lint-missing-docs - #![warn(missing_docs)]
lint-unsafe-doc - #![warn(clippy::undocumented_unsafe_blocks)]
lint-cargo-metadata - #![warn(clippy::cargo)] for published crates
lint-rustfmt-check - Run cargo fmt --check in CI
lint-workspace-lints - Configure lints at workspace level
14. Anti-patterns (REFERENCE)
anti-unwrap-abuse - Don't use .unwrap() in production code
anti-expect-lazy - Don't use .expect() for recoverable errors
anti-clone-excessive - Don't clone when borrowing works
anti-lock-across-await - Don't hold locks across .await
anti-string-for-str - Don't accept &String when &str works
anti-vec-for-slice - Don't accept &Vec when &[T] works
anti-index-over-iter - Don't use indexing when iterators work
anti-panic-expected - Don't panic on expected/recoverable errors
anti-empty-catch - Don't use empty if let Err(_) = ... blocks
anti-over-abstraction - Don't over-abstract with excessive generics
anti-premature-optimize - Don't optimize before profiling
anti-type-erasure - Don't use Box when impl Trait works
anti-format-hot-path - Don't use format!() in hot paths
anti-collect-intermediate - Don't collect() intermediate iterators
anti-stringly-typed - Don't use strings for structured data
Recommended Cargo.toml Settings
[
profile.release
]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true
[
profile.bench
]
inherits = "release"
debug = true
strip = false
[
profile.dev
]
opt-level = 0
debug = true
[
profile.dev.package."*"
]
opt-level = 3 # Optimize dependencies in dev
How to Use
This skill provides rule identifiers for quick reference. When generating or reviewing Rust code:
Check relevant category based on task type
Apply rules with matching prefix
Prioritize CRITICAL > HIGH > MEDIUM > LOW
Read rule files in rules/ for detailed examples
Rule Application by Task
Task Primary Categories
New function own-, err-, name-
New struct/API api-, type-, doc-
Async code async-, own-
Error handling err-, api-
Memory optimization mem-, own-, perf-
Performance tuning opt-, mem-, perf-
Code review anti-, lint-
Sources
This skill synthesizes best practices from:
Rust API Guidelines
Rust Performance Book
Rust Design Patterns
Production codebases: ripgrep, tokio, serde, polars, axum, deno
Clippy lint documentation
Community conventions (2024-2025)