Rust Best Practices
Quick Start
Apply these rules by default when touching Rust:
- •Organize code by feature/module, not by file type
- •Keep structs small and focused; split large data into composable types
- •Prefer
Result<T, E>for recoverable errors, avoidpanic! - •Document every
unsafeblock with a// SAFETY:rationale - •Default to
Vec/HashMap; pre-allocate when size is known
Workflow (use this order)
- •Clarify scope: new module, refactor, or review.
- •Organize by feature/module; keep type + impls together.
- •Ensure error handling uses
Result(avoidpanic!except invariants). - •Document
unsafeblocks with// SAFETY:and minimal scope. - •Choose data structures intentionally and pre-allocate when size is known.
- •Add or update tests (unit tests + rustdoc examples for public APIs).
Review Checklist
- •Module layout is feature-driven and cohesive.
- •
Resultused for recoverable errors;panic!justified. - •All
unsafeblocks have explicit// SAFETY:rationale. - •No oversized structs; data is composed cleanly.
- •Collections pre-allocated when size is known.
- •Tests and doc examples cover edge cases.
Local Resources
Use the always-applied rule file in this folder:
- •
rust.mdc(authoritative Rust guidance; read and apply the full rule text)
Code Organization
- •Feature-driven modules: Keep a struct, its enums, and
implblocks together - •Small, cohesive types: Split large structs into composable pieces
Common Patterns
- •Newtype for type safety: Wrap primitives to avoid ID/value mixups
- •Builder for complex construction: Use builders for many optional fields
- •Minimal generic bounds: Put bounds on
impl/functions, not the type- •Favor
struct Foo<T> { ... }with bounds onimpl Foo<T>or methods
- •Favor
Performance Guidance
- •Default to
VecandHashMap; switch only with evidence or requirements - •Pre-allocate capacity when the approximate size is known
Error Handling and Safety
- •Use
Result<T, E>for recoverable errors - •Reserve
panic!for invariants or unrecoverable bugs - •Add a
// SAFETY:rationale before everyunsafeblock
Testing
- •Prefer unit tests with
#[test]in atestsmodule - •Use
rustdocexamples for public APIs to keep docs executable
Examples
Example 1: Newtype for IDs
Input: "Function accepts multiple u64 IDs"
Output:
rust
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct UserId(u64);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AccountId(u64);
fn process_transaction(user_id: UserId, account_id: AccountId) { /* ... */ }
Example 2: Documenting unsafe
Input: "Need to write through a raw pointer" Output:
rust
let mut vec = vec![0];
let ptr = vec.as_mut_ptr();
// SAFETY: `ptr` is valid for `vec[0]` and we write a valid value.
unsafe { *ptr = 42; }
Example 3: Builder for optional config
Input: "Many optional config fields with defaults" Output:
rust
pub struct Config { pub timeout: u64, pub retries: u8 }
#[derive(Default)]
pub struct ConfigBuilder { timeout: Option<u64>, retries: Option<u8> }
impl Config {
pub fn builder() -> ConfigBuilder { ConfigBuilder::default() }
}
impl ConfigBuilder {
pub fn timeout(mut self, timeout: u64) -> Self { self.timeout = Some(timeout); self }
pub fn retries(mut self, retries: u8) -> Self { self.retries = Some(retries); self }
pub fn build(self) -> Config {
Config {
timeout: self.timeout.unwrap_or(1000),
retries: self.retries.unwrap_or(3),
}
}
}
Example 4: Pre-allocate collection capacity
Input: "Known item count during collection building" Output:
rust
let mut items = Vec::with_capacity(1000);
for i in 0..1000 {
items.push(i);
}