Rust Operations

SkillAI & models

Rust development patterns, ownership, async, error handling, and ecosystem. Use for: rust, cargo, ownership, borrow checker, lifetime, tokio, serde, trait, Result, Option, async rust, crate, derive, impl, enum, pattern matching, Arc, Mutex, Send, Sync, thiserror, anyhow, clap, axum, sqlx, reqwest, rayon, tracing.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Rust Operations skill

What this skill tells your AI

The instructions your AI receives, as published by 0xdarkmatter/claude-mods in skills/rust-ops/SKILL.md and read by ahel’s review.

Comprehensive Rust skill covering ownership, async, error handling, and the production ecosystem.

Ecosystem facts verified as of 2026-07.

Staleness check: python scripts/check-rust-facts.py --offline asserts the catalogued version-bearing facts (tokio, axum, serde) are still named in the prose and the dated currency note above is present; run --live to confirm each crate's crates.io major still matches the documented major. Catalog: assets/rust-facts.json.

Ownership Quick Reference

Who owns the value?
│
├─ Need to transfer ownership
│  └─ Move: let s2 = s1;  (s1 is invalid after this)
│
├─ Need to read without owning
│  └─ Shared borrow: &T (multiple allowed, no mutation)
│
├─ Need to mutate without owning
│  └─ Exclusive borrow: &mut T (only one, no other borrows)
│
├─ Need to share ownership across threads
│  └─ Arc<T> (atomic reference counting)
│     └─ Need mutation too? Arc<Mutex<T>>
│
├─ Need to share ownership single-threaded
│  └─ Rc<T> (reference counting, not Send)
│     └─ Need mutation too? Rc<RefCell<T>>
│
└─ Need to avoid cloning large data
   └─ Cow<'a, T> (clone-on-write, borrows when possible)

The Borrow Rules

  1. At any time, you can have either one &mut T or any number of &T
  2. References must always be valid (no dangling)
  3. These rules are enforced at compile time (zero runtime cost)

Error Handling Decision Tree

What kind of error?
│
├─ Operation might not have a value (no error info needed)
│  └─ Option<T>: Some(value) or None
│
├─ Library code (callers need to match on error variants)
│  └─ thiserror: #[derive(Error)] enum with variants
│     └─ Each variant can wrap source errors with #[from]
│
├─ Application code (just need context, not matching)
│  └─ anyhow: anyhow::Result<T>, .context("msg")
│
├─ Converting between error types
│  └─ impl From<SourceError> for MyError
│     └─ Or use #[from] with thiserror
│
└─ Truly unrecoverable (violating invariants)
   └─ panic!() or unwrap() - avoid in library code

thiserror (Library Errors)

use thiserror::Error;

#[derive(Debug, Error)]
pub enum AppError {
    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("not found: {entity} with id {id}")]
    NotFound { entity: &'static str, id: i64 },

    #[error("validation failed: {0}")]
    Validation(String),
}

anyhow (Application Errors)

use anyhow::{Context, Result};

fn load_config(path: &str) -> Result<Config> {
    let content = std::fs::read_to_string(path)
        .context("failed to read config file")?;
    let config: Config = toml::from_str(&content)
        .context("failed to parse config")?;
    Ok(config)
}

The ? Operator

// ? on Result: returns Err early, unwraps Ok
let file = File::open(path)?;

// ? on Option: returns None early, unwraps Some
let first = items.first()?;

// Chain with map_err for context
let port: u16 = env::var("PORT")
    .map_err(|_| AppError::Config("PORT not set"))?
    .parse()
    .map_err(|_| AppError::Config("PORT not a number"))?;

Deep dive: Load ./references/error-handling.md for Result/Option combinators, error conversion patterns, panic/recover.

Trait Design Quick Reference

Common Derives

#[derive(Debug, Clone, PartialEq, Eq, Hash)]  // Value types
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]  // API types
#[derive(Debug, thiserror::Error)]  // Error types

Trait Objects vs Generics

Trait Objects (dyn Trait)Generics (T: Trait)
DispatchDynamic (vtable)Static (monomorphized)
Binary sizeSmallerLarger (per-type copies)
PerformanceSlight overheadZero-cost
Heterogeneous collectionsYesNo
Use whenRuntime polymorphism, plugin systemsPerformance-critical, known types
// Generics (preferred when types known at compile time)
fn process<T: Display>(item: T) { println!("{item}"); }

// Trait objects (when you need heterogeneous collections)
fn process_all(items: &[Box<dyn Display>]) {
    for item in items { println!("{item}"); }
}

Key Traits to Know

TraitPurposeAuto-derive?
DebugDebug formattingYes
CloneExplicit copyYes
CopyImplicit copy (small, stack-only)Yes
DisplayUser-facing formattingNo (impl manually)
From/IntoType conversionNo (impl From, get Into free)
SendSafe to send between threadsAuto
SyncSafe to share references between threadsAuto
DerefSmart pointer dereferenceNo
IteratorIteration protocolNo
DefaultDefault valueYes

Deep dive: Load ./references/traits-generics.md for associated types, supertraits, sealed traits, extension traits.

Async Decision Tree

Do you need async?
│
├─ I/O-heavy (network, files, databases)
│  └─ Yes. Use tokio.
│
├─ CPU-heavy computation
│  └─ No. Use rayon for data parallelism.
│     └─ Or tokio::task::spawn_blocking for mixing with async
│
├─ Simple scripts or CLI tools
│  └─ Probably not. Blocking I/O is fine.
│
└─ Yes, I need async:
   │
   ├─ Runtime: tokio (dominant), or async-std
   ├─ HTTP client: reqwest
   ├─ HTTP server: axum (tower-based) or actix-web
   ├─ Database: sqlx (compile-time checked)
   └─ Structured logging: tracing

tokio Quick Start

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Spawn concurrent tasks
    let (a, b) = tokio::join!(
        fetch_users(),
        fetch_orders(),
    );

    // Select first to complete
    tokio::select! {
        result = long_operation() => handle(result),
        _ = tokio::time::sleep(Duration::from_secs(5)) => {
            eprintln!("timeout");
        }
    }

    Ok(())
}

Channel Types

ChannelUse CaseImport
mpscMultiple producers, single consumertokio::sync::mpsc
oneshotSingle value, single usetokio::sync::oneshot
broadcastMultiple consumers, all get every messagetokio::sync::broadcast
watchSingle value, latest-only (config reload)tokio::sync::watch

Deep dive: Load ./references/async-tokio.md for spawn patterns, graceful shutdown, Mutex choice, async traits, streams.

Cargo Quick Reference

# Create project
cargo new my-project        # binary
cargo new my-lib --lib      # library

# Build and run
cargo build                 # debug
cargo build --release       # optimized
cargo run -- args           # build + run
cargo run --example name    # run example

# Test
cargo test                  # all tests
cargo test test_name        # specific test
cargo test -- --nocapture   # show println output

# Dependencies
cargo add serde --features derive    # add dep
cargo add tokio -F full              # shorthand
cargo update                         # update lock file

# Check without building
cargo check                 # fast type checking
cargo clippy                # lints
cargo fmt                   # format

# Workspace
cargo test --workspace      # test all crates
cargo build -p my-crate     # build specific crate

Feature Flags

[features]
default = ["json"]
json = ["dep:serde_json"]
full = ["json", "yaml", "toml"]

[dependencies]
serde_json = { version = "1", optional = true }

Release Profile Tuning

[profile.release]
lto = true            # Link-time optimization: smaller, faster binaries
codegen-units = 1     # Better optimization at the cost of compile time

Common Gotchas

GotchaWhyFix
String vs &strOwned vs borrowed, function signaturesAccept &str in params, return String
Borrow checker fightBorrowing self while mutatingSplit struct, use indices, clone (if cheap)
Lifetime elision confusionHidden lifetimes in function signaturesWrite them out explicitly to understand, then elide
impl Trait in returnDifferent branches must return same typeUse Box<dyn Trait> for heterogeneous returns
tokio::Mutex vs std::Mutexstd::Mutex can't be held across .awaitUse tokio::Mutex across await points
Orphan ruleCan't impl foreign trait for foreign typeNewtype pattern: struct Wrapper(ForeignType)
Pin confusionRequired for self-referential async futuresUse Box::pin(), don't fight it
Send bounds on asyncSpawned futures must be SendAvoid Rc, RefCell in async; use Arc, Mutex
.unwrap() in productionPanics on None/ErrUse ?, .unwrap_or(), .expect("reason")

serde Quick Reference

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct User {
    user_id: i64,
    display_name: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    email: Option<String>,

    #[serde(default)]
    is_active: bool,

    #[serde(rename = "type")]
    user_type: UserType,

    #[serde(with = "chrono::serde::ts_seconds")]
    created_at: DateTime<Utc>,
}

// Serialize
let json = serde_json::to_string(&user)?;
let yaml = serde_yaml::to_string(&user)?;

// Deserialize
let user: User = serde_json::from_str(&json)?;

Deep dive: Load ./references/ecosystem.md for serde advanced usage, clap, reqwest, sqlx, axum, tracing, rayon.

Reference Files

Load these for deep-dive topics. Each is self-contained.

ReferenceWhen to Load
./references/ownership-lifetimes.mdBorrowing rules, lifetime annotations, elision, interior mutability, common borrow checker patterns
./references/traits-generics.mdTrait design, associated types, supertraits, generics, constraints, sealed/extension traits
./references/error-handling.mdResult/Option combinators, thiserror/anyhow deep dive, error conversion, panic/recover
./references/async-tokio.mdtokio runtime, spawn, channels, select, streams, graceful shutdown, async traits, Mutex choice
./references/ecosystem.mdserde advanced, clap, reqwest, sqlx, axum, tracing, rayon, itertools, Cow
./references/testing.mdUnit/integration/doc tests, async tests, mockall, proptest, criterion benchmarks

See Also

  • docker-ops - Multi-stage builds for Rust (scratch/distroless, cargo-chef for layer caching)
  • ci-cd-ops - Rust CI pipelines, cargo caching, cross-compilation
  • testing-ops - Cross-language testing strategies

Signals

GitHub stars
36
Forks
5
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
rust-ops
Source
github.com/0xdarkmatter/claude-mods