rust-patterns

Idiomatic Rust patterns, ownership, error handling, traits, concurrency, and best practices for building safe, performant applications.

INSTALLATION
npx skills add https://github.com/affaan-m/everything-claude-code --skill rust-patterns
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Rust Development Patterns

Idiomatic Rust patterns and best practices for building safe, performant, and maintainable applications.

When to Use

  • Writing new Rust code
  • Reviewing Rust code
  • Refactoring existing Rust code
  • Designing crate structure and module layout

How It Works

This skill enforces idiomatic Rust conventions across six key areas: ownership and borrowing to prevent data races at compile time, Result/? error propagation with thiserror for libraries and anyhow for applications, enums and exhaustive pattern matching to make illegal states unrepresentable, traits and generics for zero-cost abstraction, safe concurrency via Arc<Mutex<T>>, channels, and async/await, and minimal pub surfaces organized by domain.

Core Principles

1. Ownership and Borrowing

Rust's ownership system prevents data races and memory bugs at compile time.

// Good: Pass references when you don't need ownership

fn process(data: &#x26;[u8]) -> usize {

    data.len()

}

// Good: Take ownership only when you need to store or consume

fn store(data: Vec<u8>) -> Record {

    Record { payload: data }

}

// Bad: Cloning unnecessarily to avoid borrow checker

fn process_bad(data: &#x26;Vec<u8>) -> usize {

    let cloned = data.clone(); // Wasteful — just borrow

    cloned.len()

}

Use Cow for Flexible Ownership

use std::borrow::Cow;

fn normalize(input: &#x26;str) -> Cow<'_, str> {

    if input.contains(' ') {

        Cow::Owned(input.replace(' ', "_"))

    } else {

        Cow::Borrowed(input) // Zero-cost when no mutation needed

    }

}

Error Handling

Use Result and ? — Never unwrap() in Production

// Good: Propagate errors with context

use anyhow::{Context, Result};

fn load_config(path: &#x26;str) -> Result<Config> {

    let content = std::fs::read_to_string(path)

        .with_context(|| format!("failed to read config from {path}"))?;

    let config: Config = toml::from_str(&#x26;content)

        .with_context(|| format!("failed to parse config from {path}"))?;

    Ok(config)

}

// Bad: Panics on error

fn load_config_bad(path: &#x26;str) -> Config {

    let content = std::fs::read_to_string(path).unwrap(); // Panics!

    toml::from_str(&#x26;content).unwrap()

}

Library Errors with thiserror , Application Errors with anyhow

// Library code: structured, typed errors

use thiserror::Error;

#[derive(Debug, Error)]

pub enum StorageError {

    #[error("record not found: {id}")]

    NotFound { id: String },

    #[error("connection failed")]

    Connection(#[from] std::io::Error),

    #[error("invalid data: {0}")]

    InvalidData(String),

}

// Application code: flexible error handling

use anyhow::{bail, Result};

fn run() -> Result<()> {

    let config = load_config("app.toml")?;

    if config.workers == 0 {

        bail!("worker count must be > 0");

    }

    Ok(())

}

Option Combinators Over Nested Matching

// Good: Combinator chain

fn find_user_email(users: &#x26;[User], id: u64) -> Option<String> {

    users.iter()

        .find(|u| u.id == id)

        .map(|u| u.email.clone())

}

// Bad: Deeply nested matching

fn find_user_email_bad(users: &#x26;[User], id: u64) -> Option<String> {

    match users.iter().find(|u| u.id == id) {

        Some(user) => match &#x26;user.email {

            email => Some(email.clone()),

        },

        None => None,

    }

}

Enums and Pattern Matching

Model States as Enums

// Good: Impossible states are unrepresentable

enum ConnectionState {

    Disconnected,

    Connecting { attempt: u32 },

    Connected { session_id: String },

    Failed { reason: String, retries: u32 },

}

fn handle(state: &#x26;ConnectionState) {

    match state {

        ConnectionState::Disconnected => connect(),

        ConnectionState::Connecting { attempt } if *attempt > 3 => abort(),

        ConnectionState::Connecting { .. } => wait(),

        ConnectionState::Connected { session_id } => use_session(session_id),

        ConnectionState::Failed { retries, .. } if *retries < 5 => retry(),

        ConnectionState::Failed { reason, .. } => log_failure(reason),

    }

}

Exhaustive Matching — No Catch-All for Business Logic

// Good: Handle every variant explicitly

match command {

    Command::Start => start_service(),

    Command::Stop => stop_service(),

    Command::Restart => restart_service(),

    // Adding a new variant forces handling here

}

// Bad: Wildcard hides new variants

match command {

    Command::Start => start_service(),

    _ => {} // Silently ignores Stop, Restart, and future variants

}

Traits and Generics

Accept Generics, Return Concrete Types

// Good: Generic input, concrete output

fn read_all(reader: &#x26;mut impl Read) -> std::io::Result<Vec<u8>> {

    let mut buf = Vec::new();

    reader.read_to_end(&#x26;mut buf)?;

    Ok(buf)

}

// Good: Trait bounds for multiple constraints

fn process<T: Display + Send + 'static>(item: T) -> String {

    format!("processed: {item}")

}

Trait Objects for Dynamic Dispatch

// Use when you need heterogeneous collections or plugin systems

trait Handler: Send + Sync {

    fn handle(&#x26;self, request: &#x26;Request) -> Response;

}

struct Router {

    handlers: Vec<Box<dyn Handler>>,

}

// Use generics when you need performance (monomorphization)

fn fast_process<H: Handler>(handler: &#x26;H, request: &#x26;Request) -> Response {

    handler.handle(request)

}

Newtype Pattern for Type Safety

// Good: Distinct types prevent mixing up arguments

struct UserId(u64);

struct OrderId(u64);

fn get_order(user: UserId, order: OrderId) -> Result<Order> {

    // Can't accidentally swap user and order IDs

    todo!()

}

// Bad: Easy to swap arguments

fn get_order_bad(user_id: u64, order_id: u64) -> Result<Order> {

    todo!()

}

Structs and Data Modeling

Builder Pattern for Complex Construction

struct ServerConfig {

    host: String,

    port: u16,

    max_connections: usize,

}

impl ServerConfig {

    fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {

        ServerConfigBuilder { host: host.into(), port, max_connections: 100 }

    }

}

struct ServerConfigBuilder { host: String, port: u16, max_connections: usize }

impl ServerConfigBuilder {

    fn max_connections(mut self, n: usize) -> Self { self.max_connections = n; self }

    fn build(self) -> ServerConfig {

        ServerConfig { host: self.host, port: self.port, max_connections: self.max_connections }

    }

}

// Usage: ServerConfig::builder("localhost", 8080).max_connections(200).build()

Iterators and Closures

Prefer Iterator Chains Over Manual Loops

// Good: Declarative, lazy, composable

let active_emails: Vec<String> = users.iter()

    .filter(|u| u.is_active)

    .map(|u| u.email.clone())

    .collect();

// Bad: Imperative accumulation

let mut active_emails = Vec::new();

for user in &#x26;users {

    if user.is_active {

        active_emails.push(user.email.clone());

    }

}

Use collect() with Type Annotation

// Collect into different types

let names: Vec<_> = items.iter().map(|i| &#x26;i.name).collect();

let lookup: HashMap<_, _> = items.iter().map(|i| (i.id, i)).collect();

let combined: String = parts.iter().copied().collect();

// Collect Results — short-circuits on first error

let parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();

Concurrency

Arc> for Shared Mutable State

use std::sync::{Arc, Mutex};

let counter = Arc::new(Mutex::new(0));

let handles: Vec<_> = (0..10).map(|_| {

    let counter = Arc::clone(&#x26;counter);

    std::thread::spawn(move || {

        let mut num = counter.lock().expect("mutex poisoned");

        *num += 1;

    })

}).collect();

for handle in handles {

    handle.join().expect("worker thread panicked");

}

Channels for Message Passing

use std::sync::mpsc;

let (tx, rx) = mpsc::sync_channel(16); // Bounded channel with backpressure

for i in 0..5 {

    let tx = tx.clone();

    std::thread::spawn(move || {

        tx.send(format!("message {i}")).expect("receiver disconnected");

    });

}

drop(tx); // Close sender so rx iterator terminates

for msg in rx {

    println!("{msg}");

}

Async with Tokio

use tokio::time::Duration;

async fn fetch_with_timeout(url: &#x26;str) -> Result<String> {

    let response = tokio::time::timeout(

        Duration::from_secs(5),

        reqwest::get(url),

    )

    .await

    .context("request timed out")?

    .context("request failed")?;

    response.text().await.context("failed to read body")

}

// Spawn concurrent tasks

async fn fetch_all(urls: Vec<String>) -> Vec<Result<String>> {

    let handles: Vec<_> = urls.into_iter()

        .map(|url| tokio::spawn(async move {

            fetch_with_timeout(&#x26;url).await

        }))

        .collect();

    let mut results = Vec::with_capacity(handles.len());

    for handle in handles {

        results.push(handle.await.unwrap_or_else(|e| panic!("spawned task panicked: {e}")));

    }

    results

}

Unsafe Code

When Unsafe Is Acceptable

// Acceptable: FFI boundary with documented invariants (Rust 2024+)

/// # Safety

/// `ptr` must be a valid, aligned pointer to an initialized `Widget`.

unsafe fn widget_from_raw<'a>(ptr: *const Widget) -> &#x26;'a Widget {

    // SAFETY: caller guarantees ptr is valid and aligned

    unsafe { &#x26;*ptr }

}

// Acceptable: Performance-critical path with proof of correctness

// SAFETY: index is always < len due to the loop bound

unsafe { slice.get_unchecked(index) }

When Unsafe Is NOT Acceptable

// Bad: Using unsafe to bypass borrow checker

// Bad: Using unsafe for convenience

// Bad: Using unsafe without a Safety comment

// Bad: Transmuting between unrelated types

Module System and Crate Structure

Organize by Domain, Not by Type

my_app/

├── src/

│   ├── main.rs

│   ├── lib.rs

│   ├── auth/          # Domain module

│   │   ├── mod.rs

│   │   ├── token.rs

│   │   └── middleware.rs

│   ├── orders/        # Domain module

│   │   ├── mod.rs

│   │   ├── model.rs

│   │   └── service.rs

│   └── db/            # Infrastructure

│       ├── mod.rs

│       └── pool.rs

├── tests/             # Integration tests

├── benches/           # Benchmarks

└── Cargo.toml

Visibility — Expose Minimally

// Good: pub(crate) for internal sharing

pub(crate) fn validate_input(input: &#x26;str) -> bool {

    !input.is_empty()

}

// Good: Re-export public API from lib.rs

pub mod auth;

pub use auth::AuthMiddleware;

// Bad: Making everything pub

pub fn internal_helper() {} // Should be pub(crate) or private

Tooling Integration

Essential Commands

# Build and check

cargo build

cargo check              # Fast type checking without codegen

cargo clippy             # Lints and suggestions

cargo fmt                # Format code

# Testing

cargo test

cargo test -- --nocapture    # Show println output

cargo test --lib             # Unit tests only

cargo test --test integration # Integration tests only

# Dependencies

cargo audit              # Security audit

cargo tree               # Dependency tree

cargo update             # Update dependencies

# Performance

cargo bench              # Run benchmarks

Quick Reference: Rust Idioms

Idiom

Description

Borrow, don't clone

Pass &#x26;T instead of cloning unless ownership is needed

Make illegal states unrepresentable

Use enums to model valid states only

? over unwrap()

Propagate errors, never panic in library/production code

Parse, don't validate

Convert unstructured data to typed structs at the boundary

Newtype for type safety

Wrap primitives in newtypes to prevent argument swaps

Prefer iterators over loops

Declarative chains are clearer and often faster

#[must_use] on Results

Ensure callers handle return values

Cow for flexible ownership

Avoid allocations when borrowing suffices

Exhaustive matching

No wildcard _ for business-critical enums

Minimal pub surface

Use pub(crate) for internal APIs

Anti-Patterns to Avoid

// Bad: .unwrap() in production code

let value = map.get("key").unwrap();

// Bad: .clone() to satisfy borrow checker without understanding why

let data = expensive_data.clone();

process(&#x26;original, &#x26;data);

// Bad: Using String when &#x26;str suffices

fn greet(name: String) { /* should be &#x26;str */ }

// Bad: Box<dyn Error> in libraries (use thiserror instead)

fn parse(input: &#x26;str) -> Result<Data, Box<dyn std::error::Error>> { todo!() }

// Bad: Ignoring must_use warnings

let _ = validate(input); // Silently discarding a Result

// Bad: Blocking in async context

async fn bad_async() {

    std::thread::sleep(Duration::from_secs(1)); // Blocks the executor!

    // Use: tokio::time::sleep(Duration::from_secs(1)).await;

}

Remember: If it compiles, it's probably correct — but only if you avoid unwrap(), minimize unsafe, and let the type system work for you.

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card