Rust Best Practices & Architecture Ruleset
Comprehensive System Prompt / Ruleset for AI Assistants (Claude Code, Cursor)
1. Fundamental Memory & Ownership Patterns
- RAII (Resource Acquisition Is Initialization):
- Encapsulate resources (files, sockets, locks) in structs. Always rely on the automatic
Dropcall instead of manual closing/freeing.
- Encapsulate resources (files, sockets, locks) in structs. Always rely on the automatic
- Borrow Checker-Friendly Design:
- Follow the Single Ownership principle. Avoid cyclic references.
- If a break in ownership is possible, prefer Arena Allocation (e.g. via the
typed-arenacrate or index-based arrays) over a cascade ofArc<Mutex<T>>.
- Zero-Cost Abstractions & Zero-Copy:
- Use
Cow<'a, T>(Clone-On-Write) for cases where data is read more often than it’s modified. - Accept borrowed types by their Deref Target in functions (
&strinstead of&String,&[T]instead of&Vec<T>).
- Use
2. Type System & Compile-Time Guarantees
- Type-State Pattern (Compile-Time State Machine):
- Encode system state via Generics and Zero-Sized Types (
PhantomData<T>). Transitions between states must consume the object viaself(move semantics).
- Encode system state via Generics and Zero-Sized Types (
- Newtype Pattern:
- Wrap primitive types in tuple structs (
struct UserId(u64);) to rule out the classicPrimitive Obsessionmistake and mixed-up arguments.
- Wrap primitive types in tuple structs (
- Exhaustive Pattern Matching & Algebraic Data Types (ADT):
- Model mutually exclusive data via
enum. - Don’t use a wildcard
_inmatchwithout critical need, so that extending theenumautomatically triggers compile errors at every handling site.
- Model mutually exclusive data via
- Make Illegal States Unrepresentable:
- Design structs so that an invalid state of the object is impossible at the type level (no
is_valid,is_connected“flags” inside structs).
- Design structs so that an invalid state of the object is impossible at the type level (no
3. API Conventions & Standard Traits
- C-CONVENTION (Rust API Guidelines):
to_— an expensive conversion (to_string()).as_— a free borrow (as_bytes()).into_— a conversion that consumes ownership (into_vec()).
- Canonical Trait Implementations:
- For all public types, it’s mandatory to implement or derive:
Debug,Send,Sync(if safe),Default. - Instead of
parse()orfrom_...()methods, implement the canonical traitsFrom<T>,TryFrom<T>,FromStr.
- For all public types, it’s mandatory to implement or derive:
4. Error Handling Architecture
- Panic-Free Production Code:
- Full ban on
.unwrap(),.expect(),panic!(), andunreachable!()in production code.
- Full ban on
- Error Separation (Libraries vs Applications):
- Library Errors (Domain Errors): Use
thiserrorto create strictly typedenum Errortypes. - Application Errors (Contextual Errors): Use
anyhow::Resultoreyre::Resultwith added context via.context("...").
- Library Errors (Domain Errors): Use
5. Idiomatic Performance & Functional Pipeline
- Internal Iteration & Bound-Check Elimination:
- Prefer iterator chains (
map,filter,fold,collect) over explicitfor i in 0..lenloops — this lets the compiler eliminate bounds checks.
- Prefer iterator chains (
- Small-Buffer Optimization (SBO):
- Use
SmallVecorArrayVecfor collections where the average element count is small and known at compile time.
- Use
6. Safety & Unsafe Code Boundaries
- Encapsulated Unsafe & Soundness:
- All
unsafecode must be isolated in the smallest possible module with a safe wrapper.
- All
- Safety Invariant Documentation:
- Every
unsafe fnorunsafeblock must carry a comment in the format:// SAFETY: <justification for why memory invariants are upheld>.
- Every
7. Concurrency & Async
- Send & Sync Boundaries:
- Check thread safety at the type level:
Send(transfer between threads),Sync(access from multiple threads via a reference).
- Check thread safety at the type level:
- Non-Blocking Async Execution:
- Avoid any synchronous/blocking I/O or long-running CPU-bound computation inside async tasks. Use
tokio::task::spawn_blockingfor computation.
- Avoid any synchronous/blocking I/O or long-running CPU-bound computation inside async tasks. Use
8. Visibility & Modularity (Encapsulation)
- Principle of Least Privilege:
pubby default is forbidden. All internal structs and functions must bepub(crate),pub(super), or private.- Export outward (via
pub) only the crate’s final public API.
- Workspace Pattern:
- For medium and large projects, split the monolith into independent crates via
[workspace]. Each crate should own one domain.
- For medium and large projects, split the monolith into independent crates via
9. Lints & Static Analysis (Quality Control)
- Clippy as a Compiler:
- The AI must generate code that passes review with pedantic lints enabled.
- The following directives are required at the
lib.rs/main.rslevel:#![allow(unused)] #![warn(clippy::pedantic)] #![deny(clippy::unwrap_used, clippy::expect_used)] fn main() { }
10. Documentation & Doc-tests
- Executable Documentation:
- All public structs, traits, and functions (marked
pub) must have a///Rustdoc comment. - Documentation for key functions must include code examples in
```rustblocks, which automatically become integration tests (doc-tests).
- All public structs, traits, and functions (marked
- Enforce Documentation:
- For library crates, use the
#![warn(missing_docs)]directive.
- For library crates, use the
11. Testing Conventions
- Inline Unit Tests:
- Unit tests for verifying private logic should live in the same file as the code under test, in a
#[cfg(test)] mod tests { ... }module.
- Unit tests for verifying private logic should live in the same file as the code under test, in a
- Black-Box Integration Tests:
- Testing of the public API should be moved to a separate
tests/directory at the project root.
- Testing of the public API should be moved to a separate
- Trait-based Dependency Injection:
- To make dependencies mockable in tests (e.g. DB or network access), abstract them behind traits, accepting them as
&dyn Traitorimpl Trait.
- To make dependencies mockable in tests (e.g. DB or network access), abstract them behind traits, accepting them as
12. Macros Boundaries
- Compile-Time Awareness:
- Creating new custom procedural macros is forbidden without a critical need for it, since they dramatically increase compile time.
- For code generation or avoiding duplication (boilerplate), prefer declarative macros (
macro_rules!) or the Generics/Traits system.