rust-guidelines

Comprehensive Rust guidelines combining Microsoft's Pragmatic Rust Guidelines (48 production rules) with community patterns for ownership, type-driven design, domain modeling, and ecosystem integration. For specialized topics see: rust-unsafe , rust-async , rust-performance , and domain skills (rust-web , rust-cli , etc.).

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rust-guidelines" with this command: npx skills add peixotorms/odinlayer-skills/peixotorms-odinlayer-skills-rust-guidelines

Rust Guidelines

Overview

Comprehensive Rust guidelines combining Microsoft's Pragmatic Rust Guidelines (48 production rules) with community patterns for ownership, type-driven design, domain modeling, and ecosystem integration. For specialized topics see: rust-unsafe , rust-async , rust-performance , and domain skills (rust-web , rust-cli , etc.).

Full Microsoft reference: @microsoft-rust-guidelines.md

Error Handling

Context Pattern Guideline

Applications Use anyhow /eyre for convenience M-APP-ERROR

Libraries Canonical error structs with Backtrace

M-ERRORS-CANONICAL-STRUCTS

Bugs detected Panic, not Result

M-PANIC-ON-BUG

Panics Mean "stop the program", never for control flow M-PANIC-IS-STOP

Library code Never panic! — return Result

Anti-pattern #6

Silent failure Never let _ = file.write_all(data) — propagate or log Anti-pattern #5

Propagation Use ? operator, not try!() macro Coding guideline

Assertions expect("reason") over bare unwrap()

Coding guideline

// BAD: library panics on bad input pub fn parse(input: &str) -> Data { if input.is_empty() { panic!("empty"); } // ... }

// GOOD: return Result pub fn parse(input: &str) -> Result<Data, ParseError> { if input.is_empty() { return Err(ParseError::EmptyInput); } // ... }

Domain Error Strategy

Error Type Audience Recovery Example

User-facing End users Guide action InvalidEmail , NotFound

Internal Developers Debug info DatabaseError , ParseError

System Ops/SRE Monitor/alert ConnectionTimeout , RateLimited

Transient Automation Retry NetworkError , ServiceUnavailable

Permanent Human Investigate ConfigInvalid , DataCorrupted

Recovery Pattern When Implementation

Retry Transient failures Exponential backoff (tokio-retry )

Fallback Degraded mode Cached/default value

Circuit Breaker Cascading failures failsafe-rs

Timeout Slow operations tokio::time::timeout

Bulkhead Isolation Separate thread pools

#[derive(thiserror::Error, Debug)] pub enum AppError { #[error("Invalid input: {0}")] Validation(String),

#[error("Service temporarily unavailable")]
ServiceUnavailable(#[source] reqwest::Error),

#[error("Internal error")]
Internal(#[source] anyhow::Error),

}

impl AppError { pub fn is_retryable(&self) -> bool { matches!(self, Self::ServiceUnavailable(_)) } }

API Design

Rule Guideline

Accept impl AsRef<str/Path/[u8]> in functions M-IMPL-ASREF

Accept impl Read/Write for I/O (sans-io) M-IMPL-IO

Avoid Arc/Rc/Box/RefCell in public APIs M-AVOID-WRAPPERS

Prefer concrete types > generics > dyn Trait

M-DI-HIERARCHY

Use PathBuf /Path not String for filesystem M-STRONG-TYPES

Essential functionality as inherent methods, not just traits M-ESSENTIAL-FN-INHERENT

Builders for 3+ optional params M-INIT-BUILDER

Accept &str not String when you don't need ownership Anti-pattern #7

Borrow (&Config ) when you only read, don't take ownership Anti-pattern #19

Type-Driven Design

Pattern Purpose Example

Newtype Type safety for primitives struct UserId(u64);

Type State Compile-time state machine Connection<Connected>

PhantomData Lifetime/variance tracking PhantomData<&'a T>

Marker Trait Capability flag trait Validated {}

Builder Gradual construction Builder::new().name("x").build()

Sealed Trait Prevent external impl mod private { pub trait Sealed {} }

// Newtype: validate once, trust forever struct Email(String); impl Email { pub fn new(s: &str) -> Result<Self, ValidationError> { validate_email(s)?; Ok(Self(s.to_string())) } }

// Type State: compiler enforces valid transitions struct Connection<State>(TcpStream, PhantomData<State>); struct Disconnected; struct Connected; struct Authenticated;

impl Connection<Disconnected> { fn connect(self) -> Connection<Connected> { ... } } impl Connection<Connected> { fn authenticate(self) -> Connection<Authenticated> { ... } }

// BAD: stringly typed — wrong order compiles fine fn connect(host: &str, port: &str, timeout: &str) { ... } connect("8080", "localhost", "30"); // swapped!

// GOOD: strong types prevent misuse at compile time struct Host(String); struct Port(u16); struct Timeout(Duration); fn connect(host: Host, port: Port, timeout: Timeout) { ... }

// BAD: boolean parameters — unclear at call site fn fetch(url: &str, use_cache: bool, validate_ssl: bool) { ... }

// GOOD: options struct struct FetchOptions { use_cache: bool, validate_ssl: bool } fn fetch(url: &str, options: FetchOptions) { ... }

// BAD: Option<Option<T>> — ambiguous semantics fn find(id: u32) -> Option<Option<User>> { ... }

// GOOD: explicit enum enum FindResult { Found(User), NotFound, Error(String) }

Ownership & Lifetimes

Common Ownership Errors

Error Cause Fix

E0382 Use of moved value Borrow (& ), clone, or use Rc /Arc

E0597 Borrowed value doesn't live long enough Return owned value or extend lifetime

E0499 Multiple mutable borrows Sequential borrows or RefCell

E0502 Mutable borrow while immutable exists Copy value out, then mutate

E0507 Cannot move out of borrowed content Clone, or take ownership in signature

E0515 Return reference to local variable Return owned value or &'static

E0716 Temporary dropped while borrowed Bind to named variable first

Key Anti-Patterns

// BAD: clone to dodge borrow checker for item in data.clone() { println!("{}", item); } use_data(data);

// GOOD: borrow when you don't need ownership for item in &data { println!("{}", item); } use_data(data);

// BAD: holding reference prevents mutation let mut data = vec![1, 2, 3]; let first = &data[0]; data.push(4); // ERROR: data is borrowed

// GOOD: copy the value out let first = data[0]; // i32 is Copy data.push(4); // OK

// BAD: loop consumes the vector for s in strings { println!("{}", s); } println!("{:?}", strings); // ERROR: moved

// GOOD: iterate by reference for s in &strings { println!("{}", s); } println!("{:?}", strings); // OK

Lifetime Elision Rules

The compiler infers lifetimes in this order:

  • Each reference parameter gets its own lifetime

  • If exactly one input lifetime, it applies to all outputs

  • If &self /&mut self exists, its lifetime applies to all outputs

When elision doesn't work, annotate explicitly. Use meaningful names:

struct Parser<'src> { source: &'src str, // 'src not 'a — more readable }

Smart Pointers & Resource Management

Smart Pointer Decision

Type Ownership Thread-Safe Use When

Box<T>

Single Yes Heap allocation, recursive types

Rc<T>

Shared No Single-thread shared ownership

Arc<T>

Shared Yes Multi-thread shared ownership

Weak<T>

Weak ref Same as Rc/Arc Break reference cycles

Cell<T>

Single No Interior mutability (Copy types)

RefCell<T>

Single No Interior mutability (runtime check)

Decision Flowchart

Need heap allocation? ├─ Yes → Single owner? │ ├─ Yes → Box<T> │ └─ No → Multi-thread? │ ├─ Yes → Arc<T> │ └─ No → Rc<T> └─ No → Stack allocation (default)

Have reference cycles? ├─ Yes → Use Weak for one direction └─ No → Regular Rc/Arc

Need interior mutability? ├─ Yes → Thread-safe needed? │ ├─ Yes → Mutex<T> or RwLock<T> │ └─ No → T: Copy? → Cell<T> : RefCell<T> └─ No → Use &mut T

Anti-Pattern Why Bad Better

Arc everywhere Unnecessary atomic overhead Use Rc for single-thread

RefCell everywhere Runtime panics Design clear ownership

Box for small types Unnecessary allocation Stack allocation

Ignore Weak for cycles Memory leaks Design parent-child with Weak

Resource Lifecycle (RAII)

Pattern When Implementation

RAII Auto cleanup on scope exit Drop trait

Lazy init Deferred creation OnceLock , LazyLock

Pool Reuse expensive resources r2d2 , deadpool

Guard Scoped access MutexGuard pattern

Scope Transaction boundary Custom struct + Drop

// RAII Guard: cleanup on drop struct FileGuard { path: PathBuf, _handle: File }

impl Drop for FileGuard { fn drop(&mut self) { let _ = std::fs::remove_file(&self.path); } }

// Lazy Singleton (modern Rust — no lazy_static!) use std::sync::OnceLock;

static CONFIG: OnceLock<Config> = OnceLock::new();

fn get_config() -> &'static Config { CONFIG.get_or_init(|| Config::load().expect("config required")) }

Anti-Pattern Why Bad Better

Manual cleanup Easy to forget RAII/Drop

lazy_static!

External dep, deprecated std::sync::OnceLock (1.70+)

Global mutable state Thread unsafety OnceLock or proper sync

Generics & Trait Objects

Static vs Dynamic Dispatch

Pattern Dispatch Code Size Runtime Cost

fn foo<T: Trait>()

Static +bloat Zero

fn foo(x: &dyn Trait)

Dynamic Minimal vtable lookup

impl Trait return Static +bloat Zero

Box<dyn Trait>

Dynamic Minimal Allocation + vtable

When to Choose

Scenario Choose Why

Performance critical Generics Zero runtime cost

Heterogeneous collection dyn Trait

Different types at runtime

Plugin architecture dyn Trait

Unknown types at compile time

Reduce compile time dyn Trait

Less monomorphization

Small, known type set enum

No indirection

Object Safety

A trait is object-safe if it:

  • Doesn't have Self: Sized bound

  • Doesn't return Self

  • Doesn't have generic methods

  • Uses where Self: Sized for non-object-safe methods

// Static dispatch fn process(x: impl Display) { } fn process<T: Display>(x: T) { }

// Dynamic dispatch fn process(x: &dyn Display) { } fn process(x: Box<dyn Display>) { }

Documentation

Rule Guideline

Summary sentence < 15 words M-FIRST-DOC-SENTENCE

Canonical sections: Examples, Errors, Panics, Safety M-CANONICAL-DOCS

Module-level //! docs required M-MODULE-DOCS

No parameter tables — describe in prose M-CANONICAL-DOCS

#[doc(inline)] on pub use re-exports M-DOC-INLINE

Naming & Style

Rule Guideline

No weasel words (Service, Manager, Factory) M-CONCISE-NAMES

Use Builder not Factory

M-CONCISE-NAMES

Magic values need comments explaining why M-DOCUMENTED-MAGIC

Use #[expect] not #[allow] for lint overrides M-LINT-OVERRIDE-EXPECT

No get_ prefix fn name() not fn get_name()

Conversion naming as_ (cheap &), to_ (expensive), into_ (ownership)

Iterator convention iter() / iter_mut() / into_iter()

Meaningful lifetimes 'src , 'ctx not just 'a

Shadowing for transformation let x = x.parse()?

Naming: snake_case (fn/var), CamelCase (type), SCREAMING_CASE (const) Format: rustfmt (just use it) Docs: /// for public items, //! for module docs

Library Resilience

Rule Guideline

Avoid static if consistent view matters M-AVOID-STATICS

I/O and syscalls must be mockable M-MOCKABLE-SYSCALLS

No glob re-exports (pub use foo::* ) M-NO-GLOB-REEXPORTS

Test utilities behind test-util feature M-TEST-UTIL

Features must be additive M-FEATURES-ADDITIVE

Don't leak external types in public APIs M-DONT-LEAK-TYPES

Types should be Send

M-TYPES-SEND

Split crates when in doubt M-SMALLER-CRATES

Logging

Rule Guideline

Structured logging with message templates M-LOG-STRUCTURED

Named events: component.operation.state

M-LOG-STRUCTURED

OTel semantic conventions for attributes M-LOG-STRUCTURED

Redact sensitive data M-LOG-STRUCTURED

Static Verification

Recommended Cargo.toml lints:

[lints.rust] missing_debug_implementations = "warn" redundant_imports = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn"

[lints.clippy] cargo = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } correctness = { level = "warn", priority = -1 } perf = { level = "warn", priority = -1 } style = { level = "warn", priority = -1 } suspicious = { level = "warn", priority = -1 } undocumented_unsafe_blocks = "warn"

Anti-Pattern Quick Reference

Anti-Pattern Better Alternative

Clone to satisfy borrow checker Borrow with &

unwrap() everywhere Propagate with ?

String parameters &str parameters

Index loops vec[i]

Iterator loops for item in &vec

Collect then process Chain iterators

Mutex for read-heavy data RwLock

Lock held across .await

Scope the lock

std::thread::sleep in async tokio::time::sleep

Stringly typed APIs Newtype wrappers

Boolean parameters Options struct or enum

Box<T> returns unnecessarily Return T directly

Macro for simple operations Plain function

Option<Option<T>>

Custom enum with clear semantics

pub use foo::*

Explicit re-exports

#[allow(clippy::...)]

#[expect(clippy::...)]

transmute for enum casts match or TryFrom

Arc<Mutex<T>> in public API Clean interface hiding internals

String for file paths PathBuf / Path

static items across crate versions Config struct passed at init

OOP via Deref Composition, not Deref inheritance

Giant match arms Extract to methods or use enum dispatch

Over-generic everything Concrete types when possible

dyn for known types Generics for static dispatch

Rc when single owner Just use the value directly

format! for simple concatenation push_str or +

Build & Deploy

Always Use Makefile

Before running cargo build or any build command, check if a Makefile exists. If it does, use it — the Makefile encodes project-specific build steps (embedding assets, setting flags, post-build hooks) that raw commands miss.

Situation Action

Makefile exists with relevant target make deploy , make build , make test , etc.

Makefile exists, no matching target List targets, pick closest match

No Makefile Fall back to cargo build , cargo test , etc.

Permissions & Ownership

Before building or deploying, check file permissions and ownership. Fix to match the project majority with stat -c '%U:%G' * | sort | uniq -c | sort -rn | head -5 , then chown -R <user>:<group> . if needed.

Temporary Files

File type Location

Build intermediates, scratch files /tmp — never the project directory

Final build artifacts Project output dir (target/ , dist/ )

Downloaded dependencies Standard location (~/.cargo/ )

Exception: If project instructions specify a different temp location, follow those.

Test File Placement

Test type Location

Temporary (debugging, one-off) /tmp

Permanent, Rust convention Inline #[cfg(test)] mod tests in source files

Permanent, integration tests tests/ directory (create if it doesn't exist)

Specific instructions exist Follow those

Resources

  • Ecosystem Integration & Mental Models — Standard crate choices, language interop, crate selection criteria, deprecated-to-modern replacements, ownership analogies, and common misconceptions.

  • Domain Modeling — DDD-to-Rust pattern mapping (entities, aggregates, value objects, repositories), code examples, and common domain modeling mistakes.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

elementor-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

rust-cli

No summary provided by upstream source.

Repository SourceNeeds Review
General

elementor-hooks

No summary provided by upstream source.

Repository SourceNeeds Review