Rust Best Practices
SkillDocs & knowledgeComprehensive Rust coding guidelines with 265 rules across 26 categories. Use when writing, reviewing, or refactoring Rust code. Covers ownership, error handling, async patterns, concurrency, unsafe code, API design, memory optimization, performance, numeric safety, conversions, serde, pattern matching, macros, closures, observability, testing, and common anti-patterns. Invoke with /rust-skills.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Rust Best Practices skill
What this skill tells your AI
The instructions your AI receives, as published by leonardomso/rust-skills in SKILL.md and read by ahel’s review.
Comprehensive guide for writing high-quality, idiomatic, and highly optimized Rust code. Contains 265 rules across 26 categories, prioritized by impact to guide LLMs in code generation and refactoring. Current for Rust 1.96 (2024 edition).
When to Apply
Reference these guidelines when:
- Writing new Rust functions, structs, or modules
- Implementing error handling or async code
- Writing concurrent, parallel, or
unsafecode - 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- | 17 |
| 4 | Unsafe Code | CRITICAL | unsafe- | 7 |
| 5 | API Design | HIGH | api- | 17 |
| 6 | Async/Await | HIGH | async- | 18 |
| 7 | Concurrency | HIGH | conc- | 4 |
| 8 | Compiler Optimization | HIGH | opt- | 12 |
| 9 | Numeric & Arithmetic Safety | HIGH | num- | 5 |
| 10 | Type Safety | MEDIUM | type- | 13 |
| 11 | Trait & Generics Design | MEDIUM | trait- | 6 |
| 12 | Conversions | MEDIUM | conv- | 3 |
| 13 | Const & Compile-Time | MEDIUM | const- | 4 |
| 14 | Serde | MEDIUM | serde- | 8 |
| 15 | Pattern Matching | MEDIUM | pat- | 5 |
| 16 | Macros | MEDIUM | macro- | 8 |
| 17 | Closures | MEDIUM | closure- | 5 |
| 18 | Collections | MEDIUM | coll- | 4 |
| 19 | Naming Conventions | MEDIUM | name- | 16 |
| 20 | Testing | MEDIUM | test- | 15 |
| 21 | Documentation | MEDIUM | doc- | 12 |
| 22 | Observability | MEDIUM | obs- | 7 |
| 23 | Performance Patterns | MEDIUM | perf- | 13 |
| 24 | Project Structure | LOW | proj- | 14 |
| 25 | Clippy & Linting | LOW | lint- | 13 |
| 26 | Anti-patterns | REFERENCE | anti- | 15 |
Quick Reference
1. Ownership & Borrowing (CRITICAL)
own-borrow-over-clone- Prefer&Tborrowing over.clone()own-slice-over-vec- Accept&[T]not&Vec<T>,&strnot&Stringown-cow-conditional- UseCow<'a, T>for conditional ownershipown-arc-shared- UseArc<T>for thread-safe shared ownershipown-rc-single-thread- UseRc<T>for shared ownership in single-threaded contextsown-refcell-interior- UseRefCell<T>for interior mutability in single-threaded codeown-mutex-interior- UseMutex<T>for interior mutability across threadsown-rwlock-readers- UseRwLock<T>when reads significantly outnumber writesown-copy-small- ImplementCopyfor small, simple typesown-clone-explicit- Use explicitClonefor types where copying has meaningful costown-move-large- Move large types instead of copying; useBoxif moves are expensiveown-lifetime-elision- Rely on lifetime elision rules; add explicit lifetimes only when required
2. Error Handling (CRITICAL)
err-thiserror-lib- Usethiserrorfor library error typeserr-anyhow-app- Useanyhowfor application error handlingerr-result-over-panic- ReturnResult<T, E>instead of panicking for recoverable errorserr-context-chain- Add context with.context()or.with_context()err-no-unwrap-prod- Avoidunwrap()in production code; use?,expect(), or handle errorserr-expect-bugs-only- Useexpect()only for invariants that indicate bugs, not user errorserr-question-mark- Use?operator for clean propagationerr-from-impl- ImplementFrom<E>for error conversions to enable?operatorerr-source-chain- Preserve error chains with#[source]orsource()methoderr-lowercase-msg- Start error messages lowercase, no trailing punctuationerr-doc-errors- Document error conditions with# Errorssection in doc commentserr-custom-type- Define custom error types for domain-specific failures
3. Memory Optimization (CRITICAL)
mem-with-capacity- Usewith_capacity()when size is knownmem-smallvec- UseSmallVecfor usually-small collectionsmem-arrayvec- UseArrayVec<T, N>for fixed-capacity collections that never heap-allocatemem-box-large-variant- Box large enum variants to reduce overall enum sizemem-boxed-slice- UseBox<[T]>instead ofVec<T>for fixed-size heap datamem-thinvec- UseThinVec<T>for nullable collections with minimal overheadmem-clone-from- Useclone_from()to reuse allocations when repeatedly cloningmem-reuse-collections- Clear and reuse collections instead of creating new ones in loopsmem-avoid-format- Avoidformat!()when string literals workmem-write-over-format- Usewrite!()into existing buffers instead offormat!()allocationsmem-arena-allocator- Use arena allocators for batch allocationsmem-zero-copy- Use zero-copy patterns with slices andBytesmem-compact-string- Use compact string types for memory-constrained string storagemem-smaller-integers- Use appropriately-sized integers to reduce memory footprintmem-assert-type-size- Use static assertions to guard against accidental type size growthmem-take-replace- Usemem::take/mem::replaceto move a value out of a&mutwithout cloningmem-drop-order- Know and control drop order: struct fields drop top-to-bottom, locals in reverse
4. Unsafe Code (CRITICAL)
unsafe-safety-comment- Write a// SAFETY:comment above everyunsafeblock and a# Safetysection in everyunsafe fn.unsafe-minimize-scope- Keepunsafeblocks as small as possible — mark only the operation that requires unsafety, not the surrounding safe code.unsafe-miri-ci- Runcargo miri testin CI for every crate that containsunsafecode.unsafe-maybeuninit- UseMaybeUninit<T>for uninitialized memory; never usemem::uninitialized()ormem::zeroed()for types with validity invariants.unsafe-extern-block- In Rust 2024, wrapexternblocks inunsafe extern { }and annotate each item assafeorunsafe.unsafe-send-sync-manual- Document the invariants when manually implementingSendorSync; prefer letting the compiler derive them automatically.unsafe-no-mangle-unsafe- In Rust 2024, write#[unsafe(no_mangle)],#[unsafe(export_name = "...")], and#[unsafe(link_section = "...")]— not the bare attribute forms.
5. API Design (HIGH)
api-builder-pattern- Use Builder pattern for complex constructionapi-builder-must-use- Mark builder methods with#[must_use]to prevent silent dropsapi-newtype-safety- Use newtypes to prevent mixing semantically different valuesapi-typestate- Use typestate pattern to encode state machine invariants in the type systemapi-sealed-trait- Use sealed traits to prevent external implementations while allowing useapi-extension-trait- Use extension traits to add methods to external typesapi-parse-dont-validate- Parse into validated types at boundariesapi-impl-into- Acceptimpl Into<T>for flexible APIs, implementFrom<T>for conversionsapi-impl-asref- UseAsRef<T>when you only need to borrow the inner dataapi-must-use- Mark types and functions with#[must_use]when ignoring results is likely a bugapi-non-exhaustive- Use#[non_exhaustive]on public enums and structs for forward compatibilityapi-from-not-into- ImplementFrom<T>, notInto<U>- From gives you Into for freeapi-default-impl- ImplementDefaultfor types with sensible default valuesapi-common-traits- Implement standard traits (Debug, Clone, PartialEq, etc.) for public typesapi-serde-optional- Make serde a feature flag, not a hard dependency for library cratesapi-impl-fromiterator- ImplementFromIteratorandExtendfor collection types, andIntoIteratorfor all three reference formsapi-operator-overload- Overload operators only when the semantics are natural and unsurprising
6. Async/Await (HIGH)
async-tokio-runtime- Configure Tokio runtime appropriately for your workloadasync-no-lock-await- Never holdMutex/RwLockacross.awaitasync-spawn-blocking- Usespawn_blockingfor CPU-intensive workasync-tokio-fs- Usetokio::fsinstead ofstd::fsin async codeasync-cancellation-token- UseCancellationTokenfor graceful shutdown and task cancellationasync-join-parallel- Usejoin!ortry_join!for concurrent independent futuresasync-try-join- Usetry_join!for concurrent fallible operations with early return on errorasync-select-racing- Useselect!to race futures and handle the first to completeasync-bounded-channel- Use bounded channels to apply backpressure and prevent unbounded memory growthasync-mpsc-queue- Usempscchannels for async message queues between tasksasync-broadcast-pubsub- Usebroadcastchannel for pub/sub where all subscribers receive all messagesasync-watch-latest- Usewatchchannel for sharing the latest value with multiple observersasync-oneshot-response- Useoneshotchannel for request-response patternsasync-joinset-structured- UseJoinSetfor managing dynamic collections of spawned tasksasync-clone-before-await- Clone Arc/Rc data before await points to avoid holding references across suspensionasync-fn-in-trait- Use nativeasync fnin traits (stable 1.75) instead of theasync_traitmacroasync-async-fn-bounds- UseAsyncFn/AsyncFnMut/AsyncFnOncebounds instead ofF: Fn() -> Fut, Fut: Futureasync-cancel-safety- Ensure futures used intokio::select!branches are cancellation-safe
7. Concurrency (HIGH)
conc-rayon-par-iter- Use rayon'spar_iter()for CPU-bound data parallelismconc-scoped-threads- Usestd::thread::scopeto borrow stack data across threadsconc-atomic-ordering- Use the weakest correct memoryOrderingfor every atomic operationconc-thread-local- Preferthread_local!withCell/RefCelloverstatic mut
8. Compiler Optimization (HIGH)
opt-inline-small- Use#[inline]for small hot functionsopt-inline-always-rare- Use#[inline(always)]sparingly—only for critical hot paths proven by profilingopt-inline-never-cold- Use#[inline(never)]and#[cold]for error paths and rarely-executed codeopt-cold-unlikely- Mark unlikely code paths with#[cold]to help compiler optimizationopt-likely-hint- Use code structure to hint at likely branches; use intrinsics on nightlyopt-lto-release- Enable LTO in release buildsopt-codegen-units- Setcodegen-units = 1for maximum optimization in release buildsopt-pgo-profile- Use Profile-Guided Optimization (PGO) for maximum performanceopt-target-cpu- Usetarget-cpu=nativefor maximum performance on known deployment targetsopt-bounds-check- Use iterators and patterns that eliminate bounds checks in hot pathsopt-simd-portable- Use portable SIMD for vectorized operations across architecturesopt-cache-friendly- Organize data for cache-efficient access patterns
9. Numeric & Arithmetic Safety (HIGH)
num-overflow-explicit- Handle integer overflow explicitly:checked_/saturating_/wrapping_/overflowing_num-cast-try-from- Avoidasfor narrowing casts; useFromfor widening andTryFromfor narrowingnum-float-compare- Don't compare floats with==; use a tolerance, andtotal_cmpfor orderingnum-saturating-clamp- Bound values withclampand saturating arithmeticnum-nonzero- UseNonZero*types to forbid zero and unlock the niche optimization
10. Type Safety (MEDIUM)
type-newtype-ids- Wrap IDs in newtypes:UserId(u64)type-newtype-validated- Use newtypes to enforce validation at construction timetype-enum-states- Use enums for mutually exclusive statestype-option-nullable- UseOption<T>for values that might not existtype-result-fallible- UseResult<T, E>for operations that can failtype-phantom-marker- UsePhantomDatato express type relationships without runtime costtype-never-diverge- Use!(never type) for functions that never returntype-generic-bounds- Add trait bounds only where needed, prefer where clauses for readabilitytype-no-stringly- Avoid stringly-typed APIs; use enums, newtypes, or validated typestype-repr-transparent- Use#[repr(transparent)]for newtypes in FFI contextstype-deref-coercion- ImplementDeref/DerefMutonly for smart-pointer and transparent wrapper typestype-display-vs-debug- UseDisplayfor user-facing output andDebugfor diagnostics; never swap themtype-numeric-fmt- ImplementLowerHex,UpperHex,Octal, andBinaryfor numeric newtypes
11. Trait & Generics Design (MEDIUM)
trait-associated-type-vs-generic- Use an associated type when each impl has exactly one output type; use a generic parameter when a type can implement the trait for many input typestrait-blanket-impl- Use a blanket implimpl<T: Bound> Trait for Tto give behaviour to every type that satisfies a boundtrait-coherence-newtype- Respect the orphan rule; wrap a foreign type in a newtype to implement a foreign trait on ittrait-default-methods- Define a trait in terms of a few required methods plus defaulted ones built on top of themtrait-dyn-vs-generic- Choose static dispatch (generics /impl Trait) vs dynamic dispatch (dyn Trait) deliberatelytrait-object-safety- Keep a trait dyn-compatible (object-safe) when you needdyn Trait
12. Conversions (MEDIUM)
conv-tryfrom-fallible- ImplementTryFromfor fallible conversions instead of ad-hoc conversion functionsconv-fromstr-parsing- ImplementFromStrto enablestr::parsefor string-to-type conversionsconv-asmut-mutable- Acceptimpl AsMut<T>for flexible mutable borrowed inputs instead of concrete mutable references
13. Const & Compile-Time (MEDIUM)
const-block- Use inlineconst { }blocks for compile-time evaluation and assertionsconst-fn- Make functionsconst fnwhen they can run at compile timeconst-generics- Parameterize over values with const generics<const N: usize>const-vs-static- Useconstfor an inlined value andstaticfor a single addressed instance
14. Serde (MEDIUM)
serde-rename-all- Match the external naming convention with#[serde(rename_all = ...)]serde-default-compat- Use#[serde(default)]for optional and backward-compatible fieldsserde-skip-empty- Omit empty fields withskip_serializing_ifserde-flatten- Inline nested structs or capture extra keys with#[serde(flatten)]serde-enum-representation- Choose enum tagging deliberately: externally, internally, adjacently tagged, or untaggedserde-deny-unknown-fields- Reject unexpected keys with#[serde(deny_unknown_fields)]serde-custom-with- Customize a field's (de)serialization withwith/serialize_with/deserialize_withserde-try-from-validate- Validate while deserializing with#[serde(try_from = "Raw")]
15. Pattern Matching (MEDIUM)
pat-let-else- Uselet ... elsefor early-return pattern extractionpat-matches-macro- Usematches!()for boolean pattern testspat-if-let-chains- Useif letchains to combine pattern bindings and conditionspat-exhaustive-enum- Match owned enums exhaustively; avoid catch-all_that hides new variantspat-at-bindings- Use@bindings to capture a value while matching it against a pattern
16. Macros (MEDIUM)
macro-prefer-functions- Reach for a macro only when a function or generic cannot express itmacro-rules-hygiene- Rely onmacro_rules!hygiene and use$cratefor paths to your crate's itemsmacro-fragment-specifiers- Capture with precise fragment specifiers, not raw:tt, where you canmacro-export-crate-path- Export declarative macros with#[macro_export]and a clean import pathmacro-private-helpers- Hide macro-generated helper items behind a#[doc(hidden)] pub mod __privatemacro-proc-two-crate- Put procedural macros in a dedicatedproc-macro = truecrate and re-export from the facademacro-proc-syn-quote- Build procedural macros withsyn,quote, andproc-macro2macro-proc-error-spans- Report proc-macro errors as spanned compile errors, never by panicking
17. Closures (MEDIUM)
closure-fn-trait-bounds- Require the least restrictiveFntrait a callback needs (FnOnce⊇FnMut⊇Fn)closure-impl-fn-return- Return closures asimpl Fn/FnMut/FnOnce, notBox<dyn Fn>closure-move-capture- Usemovefor closures that outlive the current scope; clone beforemoveto keep the originalclosure-static-vs-dyn- Acceptimpl Fn(generic) for hot callbacks; use&dyn Fn/Box<dyn Fn>to cut code size or to store themclosure-disjoint-capture- Capture only what you use; lean on edition-2021 disjoint closure captures
18. Collections (MEDIUM)
coll-binaryheap- UseBinaryHeapfor a priority queue or repeated max-extractioncoll-map-choice- Pick the map by access pattern:HashMap(fast, unordered),BTreeMap(sorted / range queries),IndexMap(insertion order)coll-seq-choice- Default toVec; useVecDequefor queue/deque behaviour; avoidLinkedListcoll-set-membership- UseHashSet/BTreeSetfor membership tests and dedup, not linearVec::contains
19. Naming Conventions (MEDIUM)
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 505
- Forks
- 59
- Last commit
- Jun 2026
Advanced
- Catalog kind
- skill
- Gateway key
rust-skills- Source
- github.com/leonardomso/rust-skills