Claude Agent Skill · by Affaan M

Rust Patterns

Install Rust Patterns skill for Claude Code from affaan-m/everything-claude-code.

Install
Terminal · npx
$npx skills add https://github.com/wshobson/agents --skill rust-async-patterns
Works with Paperclip

How Rust Patterns fits into a Paperclip company.

Rust Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md499 lines
Expand
---name: rust-patternsdescription: Idiomatic Rust patterns, ownership, error handling, traits, concurrency, and best practices for building safe, performant applications.origin: ECC--- # 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. ```rust// Good: Pass references when you don't need ownershipfn process(data: &[u8]) -> usize {    data.len()} // Good: Take ownership only when you need to store or consumefn store(data: Vec<u8>) -> Record {    Record { payload: data }} // Bad: Cloning unnecessarily to avoid borrow checkerfn process_bad(data: &Vec<u8>) -> usize {    let cloned = data.clone(); // Wasteful — just borrow    cloned.len()}``` ### Use `Cow` for Flexible Ownership ```rustuse std::borrow::Cow; fn normalize(input: &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 ```rust// Good: Propagate errors with contextuse anyhow::{Context, Result}; fn load_config(path: &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(&content)        .with_context(|| format!("failed to parse config from {path}"))?;    Ok(config)} // Bad: Panics on errorfn load_config_bad(path: &str) -> Config {    let content = std::fs::read_to_string(path).unwrap(); // Panics!    toml::from_str(&content).unwrap()}``` ### Library Errors with `thiserror`, Application Errors with `anyhow` ```rust// Library code: structured, typed errorsuse 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 handlinguse 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 ```rust// Good: Combinator chainfn find_user_email(users: &[User], id: u64) -> Option<String> {    users.iter()        .find(|u| u.id == id)        .map(|u| u.email.clone())} // Bad: Deeply nested matchingfn find_user_email_bad(users: &[User], id: u64) -> Option<String> {    match users.iter().find(|u| u.id == id) {        Some(user) => match &user.email {            email => Some(email.clone()),        },        None => None,    }}``` ## Enums and Pattern Matching ### Model States as Enums ```rust// Good: Impossible states are unrepresentableenum ConnectionState {    Disconnected,    Connecting { attempt: u32 },    Connected { session_id: String },    Failed { reason: String, retries: u32 },} fn handle(state: &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 ```rust// Good: Handle every variant explicitlymatch command {    Command::Start => start_service(),    Command::Stop => stop_service(),    Command::Restart => restart_service(),    // Adding a new variant forces handling here} // Bad: Wildcard hides new variantsmatch command {    Command::Start => start_service(),    _ => {} // Silently ignores Stop, Restart, and future variants}``` ## Traits and Generics ### Accept Generics, Return Concrete Types ```rust// Good: Generic input, concrete outputfn read_all(reader: &mut impl Read) -> std::io::Result<Vec<u8>> {    let mut buf = Vec::new();    reader.read_to_end(&mut buf)?;    Ok(buf)} // Good: Trait bounds for multiple constraintsfn process<T: Display + Send + 'static>(item: T) -> String {    format!("processed: {item}")}``` ### Trait Objects for Dynamic Dispatch ```rust// Use when you need heterogeneous collections or plugin systemstrait Handler: Send + Sync {    fn handle(&self, request: &Request) -> Response;} struct Router {    handlers: Vec<Box<dyn Handler>>,} // Use generics when you need performance (monomorphization)fn fast_process<H: Handler>(handler: &H, request: &Request) -> Response {    handler.handle(request)}``` ### Newtype Pattern for Type Safety ```rust// Good: Distinct types prevent mixing up argumentsstruct 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 argumentsfn get_order_bad(user_id: u64, order_id: u64) -> Result<Order> {    todo!()}``` ## Structs and Data Modeling ### Builder Pattern for Complex Construction ```ruststruct 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 ```rust// Good: Declarative, lazy, composablelet active_emails: Vec<String> = users.iter()    .filter(|u| u.is_active)    .map(|u| u.email.clone())    .collect(); // Bad: Imperative accumulationlet mut active_emails = Vec::new();for user in &users {    if user.is_active {        active_emails.push(user.email.clone());    }}``` ### Use `collect()` with Type Annotation ```rust// Collect into different typeslet names: Vec<_> = items.iter().map(|i| &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 errorlet parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();``` ## Concurrency ### `Arc<Mutex<T>>` for Shared Mutable State ```rustuse std::sync::{Arc, Mutex}; let counter = Arc::new(Mutex::new(0));let handles: Vec<_> = (0..10).map(|_| {    let counter = Arc::clone(&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 ```rustuse 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 ```rustuse tokio::time::Duration; async fn fetch_with_timeout(url: &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 tasksasync fn fetch_all(urls: Vec<String>) -> Vec<Result<String>> {    let handles: Vec<_> = urls.into_iter()        .map(|url| tokio::spawn(async move {            fetch_with_timeout(&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 ```rust// 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) -> &'a Widget {    // SAFETY: caller guarantees ptr is valid and aligned    unsafe { &*ptr }} // Acceptable: Performance-critical path with proof of correctness// SAFETY: index is always < len due to the loop boundunsafe { slice.get_unchecked(index) }``` ### When Unsafe Is NOT Acceptable ```rust// 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 ```textmy_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 ```rust// Good: pub(crate) for internal sharingpub(crate) fn validate_input(input: &str) -> bool {    !input.is_empty()} // Good: Re-export public API from lib.rspub mod auth;pub use auth::AuthMiddleware; // Bad: Making everything pubpub fn internal_helper() {} // Should be pub(crate) or private``` ## Tooling Integration ### Essential Commands ```bash# Build and checkcargo buildcargo check              # Fast type checking without codegencargo clippy             # Lints and suggestionscargo fmt                # Format code # Testingcargo testcargo test -- --nocapture    # Show println outputcargo test --lib             # Unit tests onlycargo test --test integration # Integration tests only # Dependenciescargo audit              # Security auditcargo tree               # Dependency treecargo update             # Update dependencies # Performancecargo bench              # Run benchmarks``` ## Quick Reference: Rust Idioms | Idiom | Description ||-------|-------------|| Borrow, don't clone | Pass `&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 ```rust// Bad: .unwrap() in production codelet value = map.get("key").unwrap(); // Bad: .clone() to satisfy borrow checker without understanding whylet data = expensive_data.clone();process(&original, &data); // Bad: Using String when &str sufficesfn greet(name: String) { /* should be &str */ } // Bad: Box<dyn Error> in libraries (use thiserror instead)fn parse(input: &str) -> Result<Data, Box<dyn std::error::Error>> { todo!() } // Bad: Ignoring must_use warningslet _ = validate(input); // Silently discarding a Result // Bad: Blocking in async contextasync 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.