Claude Skill

rust-ops

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, ax

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download 0xdarkmatter-claude-mods-skills_rust-ops-3dfaf0b.zip · 52 KB
Part of 0xdarkmatter/claude-mods — 94 skills

Install

skills CLI npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/rust-ops
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
Git git clone https://github.com/0xDarkMatter/claude-mods.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Rust Operations

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)
Dispatch Dynamic (vtable) Static (monomorphized)
Binary size Smaller Larger (per-type copies)
Performance Slight overhead Zero-cost
Heterogeneous collections Yes No
Use when Runtime polymorphism, plugin systems Performance-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

Trait Purpose Auto-derive?
Debug Debug formatting Yes
Clone Explicit copy Yes
Copy Implicit copy (small, stack-only) Yes
Display User-facing formatting No (impl manually)
From/Into Type conversion No (impl From, get Into free)
Send Safe to send between threads Auto
Sync Safe to share references between threads Auto
Deref Smart pointer dereference No
Iterator Iteration protocol No
Default Default value Yes

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

Channel Use Case Import
mpsc Multiple producers, single consumer tokio::sync::mpsc
oneshot Single value, single use tokio::sync::oneshot
broadcast Multiple consumers, all get every message tokio::sync::broadcast
watch Single 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

Gotcha Why Fix
String vs &str Owned vs borrowed, function signatures Accept &str in params, return String
Borrow checker fight Borrowing self while mutating Split struct, use indices, clone (if cheap)
Lifetime elision confusion Hidden lifetimes in function signatures Write them out explicitly to understand, then elide
impl Trait in return Different branches must return same type Use Box<dyn Trait> for heterogeneous returns
tokio::Mutex vs std::Mutex std::Mutex can't be held across .await Use tokio::Mutex across await points
Orphan rule Can't impl foreign trait for foreign type Newtype pattern: struct Wrapper(ForeignType)
Pin confusion Required for self-referential async futures Use Box::pin(), don't fight it
Send bounds on async Spawned futures must be Send Avoid Rc, RefCell in async; use Arc, Mutex
.unwrap() in production Panics on None/Err Use ?, .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.

Reference When to Load
./references/ownership-lifetimes.md Borrowing rules, lifetime annotations, elision, interior mutability, common borrow checker patterns
./references/traits-generics.md Trait design, associated types, supertraits, generics, constraints, sealed/extension traits
./references/error-handling.md Result/Option combinators, thiserror/anyhow deep dive, error conversion, panic/recover
./references/async-tokio.md tokio runtime, spawn, channels, select, streams, graceful shutdown, async traits, Mutex choice
./references/ecosystem.md serde advanced, clap, reqwest, sqlx, axum, tracing, rayon, itertools, Cow
./references/testing.md Unit/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
Files (claude-mods)
  • assets
    • rust-facts.json 1.3 KB
      {
        "_comment": "Canonical fast-moving facts the rust-ops skill encodes. scripts/check-rust-facts.py asserts SKILL.md + references name these consistently (--offline) and probes crates.io for major-version drift (--live). Edit deliberately: a change here is a skill-content decision, not housekeeping.",
        "schema": "claude-mods.rust-ops.facts/v1",
        "as_of": "2026-07-05",
        "registry": "https://crates.io/api/v1/crates",
        "currency_note": "> Ecosystem facts verified as of 2026-07.",
        "packages": [
          {
            "name": "tokio",
            "documented_major": 1,
            "role": "async runtime (dominant)",
            "where": "SKILL.md (#[tokio::main], tokio::join!/select!, mpsc/oneshot/broadcast/watch channels); references/async-tokio.md. The #[tokio::main]/#[tokio::test] + tokio::join! surface is the Tokio 1.x API."
          },
          {
            "name": "axum",
            "documented_major": 0,
            "role": "HTTP server (tower-based)",
            "where": "SKILL.md (async decision tree: 'axum (tower-based)'); references/ecosystem.md (axum::Router, axum::serve, extractors). Axum has shipped as 0.x throughout."
          },
          {
            "name": "serde",
            "documented_major": 1,
            "role": "serialization",
            "where": "SKILL.md (#[derive(Serialize, Deserialize)], serde_json::to_string); references/ecosystem.md. The derive + serde_json surface is the Serde 1.x API."
          }
        ]
      }
      
  • references
    • async-tokio.md 22.4 KB
      # Rust Async and Tokio Reference
      
      ## Table of Contents
      
      1. [tokio Runtime](#1-tokio-runtime)
      2. [Spawn Tasks](#2-spawn-tasks)
      3. [Select](#3-select)
      4. [Channels](#4-channels)
      5. [Async Streams](#5-async-streams)
      6. [Timeouts and Sleep](#6-timeouts-and-sleep)
      7. [Async Traits](#7-async-traits)
      8. [Mutex Choice](#8-mutex-choice)
      9. [Graceful Shutdown](#9-graceful-shutdown)
      10. [Connection Pooling](#10-connection-pooling)
      11. [Test Async Code](#11-test-async-code)
      12. [Common Async Mistakes](#12-common-async-mistakes)
      
      ---
      
      ## 1. tokio Runtime
      
      ### #[tokio::main]
      
      ```rust
      // Multi-threaded (default): uses all CPU cores
      #[tokio::main]
      async fn main() -> anyhow::Result<()> {
          run().await
      }
      
      // Single-threaded: useful for tests or embedded contexts
      #[tokio::main(flavor = "current_thread")]
      async fn main() -> anyhow::Result<()> {
          run().await
      }
      
      // With worker count
      #[tokio::main(worker_threads = 4)]
      async fn main() -> anyhow::Result<()> {
          run().await
      }
      ```
      
      ### runtime::Builder
      
      ```rust
      use tokio::runtime::Builder;
      
      fn main() -> anyhow::Result<()> {
          let rt = Builder::new_multi_thread()
              .worker_threads(4)
              .thread_name("my-worker")
              .thread_stack_size(3 * 1024 * 1024)
              .enable_all()                 // enables both io and time drivers
              .build()?;
      
          rt.block_on(async {
              run().await
          })
      }
      
      // current_thread runtime for single-threaded executors
      let rt = Builder::new_current_thread()
          .enable_all()
          .build()?;
      ```
      
      ### runtime::Handle
      
      ```rust
      use tokio::runtime::Handle;
      
      // Obtain a handle from within an async context
      let handle = Handle::current();
      
      // Spawn onto the runtime from a sync context
      std::thread::spawn(move || {
          handle.spawn(async { do_work().await });
          handle.block_on(async { do_sync_work().await });
      });
      
      // try_current(): returns None outside of a runtime
      if let Ok(handle) = Handle::try_current() {
          handle.spawn(async { background_task().await });
      }
      ```
      
      ---
      
      ## 2. Spawn Tasks
      
      ### tokio::spawn and JoinHandle
      
      ```rust
      use tokio::task::JoinHandle;
      
      async fn run() {
          // spawn returns JoinHandle<T>
          let handle: JoinHandle<u32> = tokio::spawn(async {
              compute().await
          });
      
          // await the result — JoinHandle<T> returns Result<T, JoinError>
          match handle.await {
              Ok(value) => println!("got {value}"),
              Err(e) if e.is_panic() => eprintln!("task panicked"),
              Err(e) => eprintln!("task cancelled: {e}"),
          }
      }
      ```
      
      ### JoinSet for Multiple Tasks
      
      ```rust
      use tokio::task::JoinSet;
      
      async fn fetch_all(urls: Vec<String>) -> Vec<Result<String, reqwest::Error>> {
          let mut set = JoinSet::new();
      
          for url in urls {
              set.spawn(async move { reqwest::get(&url).await?.text().await });
          }
      
          let mut results = Vec::new();
          while let Some(res) = set.join_next().await {
              results.push(res.expect("task panicked"));
          }
          results
      }
      
      // Abort all remaining tasks when JoinSet drops (or explicitly)
      set.abort_all();
      ```
      
      ### abort
      
      ```rust
      let handle = tokio::spawn(async {
          loop {
              do_work().await;
          }
      });
      
      // Cancel the task
      handle.abort();
      
      // abort() is best-effort: the task must be at an await point
      // Check completion after abort
      match handle.await {
          Err(e) if e.is_cancelled() => println!("task was cancelled"),
          _ => {}
      }
      ```
      
      ### spawn_blocking for CPU Work
      
      ```rust
      // Never block the async executor — offload CPU work to a thread pool
      async fn hash_password(password: String) -> String {
          tokio::task::spawn_blocking(move || {
              bcrypt::hash(&password, 12).unwrap()
          })
          .await
          .expect("blocking task panicked")
      }
      
      // spawn_blocking has a default limit of 512 threads
      // For unbounded work, consider a dedicated rayon thread pool
      async fn process_image(data: Vec<u8>) -> Vec<u8> {
          tokio::task::spawn_blocking(move || {
              rayon_heavy_transform(data)
          })
          .await
          .unwrap()
      }
      ```
      
      ---
      
      ## 3. Select
      
      ### tokio::select! Basics
      
      ```rust
      use tokio::select;
      
      async fn race() -> &'static str {
          select! {
              result = task_a() => {
                  println!("A won: {result:?}");
                  "a"
              }
              result = task_b() => {
                  println!("B won: {result:?}");
                  "b"
              }
          }
          // Unselected branch future is dropped immediately
      }
      ```
      
      ### biased for Deterministic Priority
      
      ```rust
      select! {
          biased;  // arms checked top-to-bottom, not randomly
      
          // shutdown takes priority over incoming work
          _ = shutdown_signal() => {
              cleanup().await;
          }
          msg = receiver.recv() => {
              process(msg).await;
          }
      }
      ```
      
      ### Cancellation Safety
      
      ```rust
      // Only use futures that are cancellation-safe in select!
      // Safe: recv(), accept(), sleep(), read_line()
      // NOT safe: read_to_end(), write_all() (partial progress is lost)
      
      // For non-cancellation-safe futures, pin them and reuse
      let mut read_future = Box::pin(file.read_to_end(&mut buf));
      
      loop {
          select! {
              result = &mut read_future => {
                  // Only enters here when done, previous progress preserved
                  break result;
              }
              _ = shutdown.recv() => {
                  break Err(io::Error::new(io::ErrorKind::Interrupted, "shutdown"));
              }
          }
      }
      ```
      
      ### loop + select Pattern
      
      ```rust
      async fn event_loop(
          mut rx: mpsc::Receiver<Message>,
          mut shutdown: broadcast::Receiver<()>,
      ) {
          loop {
              select! {
                  Some(msg) = rx.recv() => {
                      handle_message(msg).await;
                  }
                  _ = shutdown.recv() => {
                      tracing::info!("shutting down event loop");
                      break;
                  }
                  else => {
                      // All branches are disabled (channels closed)
                      break;
                  }
              }
          }
      }
      ```
      
      ---
      
      ## 4. Channels
      
      ### mpsc — Multi-Producer, Single-Consumer
      
      ```rust
      use tokio::sync::mpsc;
      
      // Bounded channel (backpressure built-in)
      let (tx, mut rx) = mpsc::channel::<String>(32);
      
      // Clone the sender for multiple producers
      let tx2 = tx.clone();
      
      tokio::spawn(async move {
          tx.send("hello".to_string()).await.unwrap();
      });
      tokio::spawn(async move {
          tx2.send("world".to_string()).await.unwrap();
      });
      
      while let Some(msg) = rx.recv().await {
          println!("received: {msg}");
      }
      // recv() returns None when all senders are dropped
      
      // Unbounded channel (no backpressure — use carefully)
      let (tx, mut rx) = mpsc::unbounded_channel::<String>();
      tx.send("hello".to_string()).unwrap();  // non-async send
      ```
      
      ### oneshot — Single Value
      
      ```rust
      use tokio::sync::oneshot;
      
      // One sender, one receiver, one value
      let (tx, rx) = oneshot::channel::<u64>();
      
      tokio::spawn(async move {
          let result = compute().await;
          tx.send(result).ok();  // ok() because receiver might have dropped
      });
      
      match rx.await {
          Ok(value) => println!("computed: {value}"),
          Err(_) => println!("sender dropped before sending"),
      }
      
      // Common pattern: request-response over a channel
      struct Request {
          data: Vec<u8>,
          reply: oneshot::Sender<Result<Vec<u8>, Error>>,
      }
      ```
      
      ### broadcast — Single-Producer, Multi-Consumer
      
      ```rust
      use tokio::sync::broadcast;
      
      // All active receivers get every message
      let (tx, mut rx1) = broadcast::channel::<String>(16);
      let mut rx2 = tx.subscribe();
      
      tx.send("announcement".to_string()).unwrap();
      
      // Each receiver gets its own copy
      let msg1 = rx1.recv().await.unwrap();
      let msg2 = rx2.recv().await.unwrap();
      
      // Lagged receiver: if receiver falls behind capacity, it gets Err(Lagged(n))
      match rx1.recv().await {
          Ok(msg) => handle(msg),
          Err(broadcast::error::RecvError::Lagged(n)) => {
              tracing::warn!("missed {n} messages, resyncing");
          }
          Err(broadcast::error::RecvError::Closed) => break,
      }
      ```
      
      ### watch — Single Writer, Multi-Reader (Latest Value)
      
      ```rust
      use tokio::sync::watch;
      
      // Only the most recent value is retained
      let (tx, rx) = watch::channel(Config::default());
      
      // Writer updates the shared value
      tokio::spawn(async move {
          loop {
              let new_config = reload_config().await;
              tx.send(new_config).unwrap();
              tokio::time::sleep(Duration::from_secs(30)).await;
          }
      });
      
      // Readers clone a receiver and watch for changes
      tokio::spawn(async move {
          let mut rx = rx;
          loop {
              rx.changed().await.unwrap();  // waits for a new value
              let config = rx.borrow_and_update().clone();
              apply_config(config).await;
          }
      });
      ```
      
      ---
      
      ## 5. Async Streams
      
      ### Stream Trait and StreamExt
      
      ```rust
      use futures::StreamExt;  // or tokio_stream::StreamExt
      
      // Streams are async iterators
      async fn process_stream<S>(mut stream: S)
      where
          S: futures::Stream<Item = Event> + Unpin,
      {
          while let Some(event) = stream.next().await {
              handle(event).await;
          }
      
          // StreamExt combinators
          stream.map(|e| transform(e))
                .filter(|e| futures::future::ready(e.important))
                .take(100)
                .for_each(|e| async move { handle(e).await })
                .await;
      }
      ```
      
      ### tokio_stream
      
      ```rust
      use tokio_stream::{self as stream, StreamExt};
      
      // Stream from iterator
      let s = stream::iter(vec![1, 2, 3]);
      
      // Stream with delay between items
      let s = stream::iter(vec![1, 2, 3])
          .throttle(Duration::from_millis(100));
      
      // Merge streams
      let s = stream::select(stream_a, stream_b);
      
      // Wrap a channel receiver as a stream
      let stream = tokio_stream::wrappers::ReceiverStream::new(rx);
      let stream = tokio_stream::wrappers::BroadcastStream::new(rx);
      let stream = tokio_stream::wrappers::WatchStream::new(rx);
      ```
      
      ### Create Streams with async_stream
      
      ```rust
      use async_stream::stream;
      
      fn paginate(client: Client, query: Query) -> impl futures::Stream<Item = Record> {
          stream! {
              let mut cursor = None;
              loop {
                  let page = client.fetch(query.clone(), cursor).await.unwrap();
                  for record in page.records {
                      yield record;
                  }
                  match page.next_cursor {
                      Some(c) => cursor = Some(c),
                      None => break,
                  }
              }
          }
      }
      ```
      
      ---
      
      ## 6. Timeouts and Sleep
      
      ### tokio::time::sleep
      
      ```rust
      use tokio::time::{sleep, Duration};
      
      // Non-blocking sleep
      sleep(Duration::from_secs(1)).await;
      
      // Sleep until a specific instant
      use tokio::time::Instant;
      sleep(Instant::now() + Duration::from_millis(500) - Instant::now()).await;
      ```
      
      ### timeout
      
      ```rust
      use tokio::time::timeout;
      
      match timeout(Duration::from_secs(5), fetch_data()).await {
          Ok(Ok(data)) => process(data),
          Ok(Err(e)) => handle_error(e),
          Err(_elapsed) => eprintln!("request timed out"),
      }
      
      // timeout returns Err(Elapsed) on timeout
      // The inner future is cancelled when timeout fires
      ```
      
      ### interval
      
      ```rust
      use tokio::time::{interval, MissedTickBehavior};
      
      async fn heartbeat() {
          let mut ticker = interval(Duration::from_secs(10));
          ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
      
          loop {
              ticker.tick().await;
              send_heartbeat().await;
          }
      }
      
      // MissedTickBehavior options:
      // Burst (default): catch up all missed ticks immediately
      // Skip: skip missed ticks, tick at next aligned interval
      // Delay: delay next tick by full interval from now
      ```
      
      ### Instant
      
      ```rust
      use tokio::time::Instant;
      
      let start = Instant::now();
      do_work().await;
      let elapsed = start.elapsed();
      tracing::info!(?elapsed, "work completed");
      ```
      
      ---
      
      ## 7. Async Traits
      
      ### RPITIT (Rust 1.75+, Preferred)
      
      ```rust
      // Async functions in traits work natively since Rust 1.75
      pub trait DataStore {
          async fn get(&self, key: &str) -> Option<String>;
          async fn set(&self, key: &str, value: String) -> Result<(), Error>;
      }
      
      impl DataStore for RedisStore {
          async fn get(&self, key: &str) -> Option<String> {
              self.client.get(key).await.ok()
          }
      
          async fn set(&self, key: &str, value: String) -> Result<(), Error> {
              self.client.set(key, value).await?;
              Ok(())
          }
      }
      ```
      
      ### trait_variant for dyn Compatibility
      
      ```rust
      // Native async traits are not dyn-safe by default
      // Use trait_variant for trait objects
      use trait_variant::make;
      
      #[make(SendDataStore: Send)]
      pub trait DataStore {
          async fn get(&self, key: &str) -> Option<String>;
      }
      
      // Now use dyn SendDataStore for boxed trait objects
      fn make_store() -> Box<dyn SendDataStore> {
          Box::new(RedisStore::new())
      }
      ```
      
      ### async-trait Crate (Pre-1.75 or dyn-safe)
      
      ```rust
      use async_trait::async_trait;
      
      #[async_trait]
      pub trait Handler: Send + Sync {
          async fn handle(&self, req: Request) -> Response;
      }
      
      #[async_trait]
      impl Handler for MyHandler {
          async fn handle(&self, req: Request) -> Response {
              process(req).await
          }
      }
      
      // async_trait boxes the returned future automatically
      // Zero-cost in practice but adds a heap allocation per call
      ```
      
      ### Manual Approach (Maximum Control)
      
      ```rust
      use std::future::Future;
      use std::pin::Pin;
      
      pub trait Handler: Send + Sync {
          fn handle<'a>(
              &'a self,
              req: Request,
          ) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>>;
      }
      
      impl Handler for MyHandler {
          fn handle<'a>(&'a self, req: Request) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>> {
              Box::pin(async move { process(&self.state, req).await })
          }
      }
      ```
      
      ---
      
      ## 8. Mutex Choice
      
      ### tokio::sync::Mutex vs std::sync::Mutex
      
      ```rust
      // Use std::sync::Mutex when:
      // - Lock is held only for synchronous operations (no .await inside lock)
      // - Lock contention is low
      // - You want lower overhead
      
      use std::sync::Mutex;
      
      struct Cache {
          inner: Mutex<HashMap<String, String>>,
      }
      
      impl Cache {
          fn get(&self, key: &str) -> Option<String> {
              self.inner.lock().unwrap().get(key).cloned()
          }
      
          async fn get_or_fetch(&self, key: &str) -> String {
              if let Some(v) = self.get(key) {
                  return v;
              }
              let value = fetch(key).await;  // lock NOT held during await
              self.inner.lock().unwrap().insert(key.to_string(), value.clone());
              value
          }
      }
      ```
      
      ```rust
      // Use tokio::sync::Mutex when:
      // - You need to hold the lock across .await points
      // - Multiple async tasks contend and fairness matters
      
      use tokio::sync::Mutex;
      
      struct Connection {
          inner: Mutex<TcpStream>,
      }
      
      impl Connection {
          async fn send_and_receive(&self, data: &[u8]) -> Vec<u8> {
              let mut conn = self.inner.lock().await;  // lock held across awaits
              conn.write_all(data).await.unwrap();
              let mut buf = vec![0u8; 1024];
              conn.read(&mut buf).await.unwrap();
              buf
          }
      }
      ```
      
      ### RwLock
      
      ```rust
      use tokio::sync::RwLock;
      
      struct Config {
          data: RwLock<ConfigData>,
      }
      
      impl Config {
          async fn get(&self) -> ConfigData {
              self.data.read().await.clone()  // multiple concurrent readers
          }
      
          async fn update(&self, new: ConfigData) {
              *self.data.write().await = new;  // exclusive writer
          }
      }
      
      // RwLock can starve writers if readers are constant — monitor in practice
      ```
      
      ---
      
      ## 9. Graceful Shutdown
      
      ### Signal Handling
      
      ```rust
      use tokio::signal;
      
      async fn wait_for_shutdown() {
          let ctrl_c = async {
              signal::ctrl_c().await.expect("failed to install Ctrl+C handler");
          };
      
          #[cfg(unix)]
          let sigterm = async {
              signal::unix::signal(signal::unix::SignalKind::terminate())
                  .expect("failed to install SIGTERM handler")
                  .recv()
                  .await;
          };
      
          #[cfg(not(unix))]
          let sigterm = std::future::pending::<()>();
      
          tokio::select! {
              _ = ctrl_c => {},
              _ = sigterm => {},
          }
          tracing::info!("shutdown signal received");
      }
      ```
      
      ### CancellationToken
      
      ```rust
      use tokio_util::sync::CancellationToken;
      
      #[tokio::main]
      async fn main() {
          let token = CancellationToken::new();
      
          // Give child tasks a clone
          let worker_token = token.child_token();
          tokio::spawn(async move {
              select! {
                  _ = worker_token.cancelled() => {
                      tracing::info!("worker shutting down");
                  }
                  _ = do_work() => {}
              }
          });
      
          // Trigger shutdown on signal
          wait_for_shutdown().await;
          token.cancel();
      
          // Give tasks time to drain
          tokio::time::sleep(Duration::from_secs(5)).await;
      }
      ```
      
      ### Draining Connections and Shutdown Sequence
      
      ```rust
      async fn shutdown(
          server: Server,
          mut rx: mpsc::Receiver<()>,
          token: CancellationToken,
      ) {
          // 1. Stop accepting new connections
          server.stop_accepting();
      
          // 2. Signal all workers
          token.cancel();
      
          // 3. Wait for in-flight requests (with timeout)
          let drain = timeout(Duration::from_secs(30), server.drain());
          match drain.await {
              Ok(_) => tracing::info!("clean shutdown"),
              Err(_) => tracing::warn!("shutdown timeout: forcing exit"),
          }
      
          // 4. Flush telemetry, close DB pools, etc.
          flush_telemetry().await;
      }
      ```
      
      ---
      
      ## 10. Connection Pooling
      
      ### sqlx Pool
      
      ```rust
      use sqlx::postgres::PgPoolOptions;
      
      let pool = PgPoolOptions::new()
          .max_connections(20)
          .min_connections(2)
          .acquire_timeout(Duration::from_secs(5))
          .idle_timeout(Duration::from_secs(600))
          .connect("postgres://user:pass@localhost/db")
          .await?;
      
      // Clone the pool cheaply — it's Arc internally
      async fn get_user(pool: &sqlx::PgPool, id: i64) -> sqlx::Result<User> {
          sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
              .fetch_one(pool)
              .await
      }
      ```
      
      ### reqwest Client Reuse
      
      ```rust
      use reqwest::Client;
      
      // Build once, clone cheaply (Arc internally)
      let client = Client::builder()
          .timeout(Duration::from_secs(30))
          .pool_max_idle_per_host(10)
          .connection_verbose(false)
          .build()?;
      
      // Share via state, not by creating new clients per request
      #[derive(Clone)]
      struct AppState {
          http: Client,
          db: sqlx::PgPool,
      }
      ```
      
      ### bb8 Generic Pool
      
      ```rust
      use bb8::Pool;
      use bb8_redis::RedisConnectionManager;
      
      let manager = RedisConnectionManager::new("redis://localhost")?;
      let pool = Pool::builder()
          .max_size(15)
          .min_idle(Some(2))
          .connection_timeout(Duration::from_secs(3))
          .build(manager)
          .await?;
      
      let mut conn = pool.get().await?;
      redis::cmd("SET").arg("key").arg("value").query_async(&mut *conn).await?;
      ```
      
      ---
      
      ## 11. Test Async Code
      
      ### #[tokio::test]
      
      ```rust
      #[tokio::test]
      async fn test_fetch_user() {
          let db = setup_test_db().await;
          let user = db.get_user(1).await.unwrap();
          assert_eq!(user.name, "Alice");
      }
      
      // Multi-thread flavor for concurrency tests
      #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
      async fn test_concurrent_writes() {
          let state = Arc::new(SharedState::new());
          let handles: Vec<_> = (0..10)
              .map(|i| {
                  let s = state.clone();
                  tokio::spawn(async move { s.write(i).await })
              })
              .collect();
          futures::future::join_all(handles).await;
          assert_eq!(state.count().await, 10);
      }
      ```
      
      ### Mock Time with tokio::time::pause
      
      ```rust
      #[tokio::test]
      async fn test_timeout_behavior() {
          tokio::time::pause();  // freeze time
      
          let result = tokio::spawn(async {
              timeout(Duration::from_secs(5), slow_operation()).await
          });
      
          // Advance time without actually waiting
          tokio::time::advance(Duration::from_secs(6)).await;
      
          let outcome = result.await.unwrap();
          assert!(outcome.is_err(), "should have timed out");
      }
      ```
      
      ### Testing Channels
      
      ```rust
      #[tokio::test]
      async fn test_message_processing() {
          let (tx, rx) = mpsc::channel(8);
          let processor = Processor::new(rx);
      
          let handle = tokio::spawn(processor.run());
      
          tx.send(Message::Ping).await.unwrap();
          tx.send(Message::Stop).await.unwrap();
          drop(tx);
      
          handle.await.unwrap();
      }
      
      // Test that a task completes within expected time
      #[tokio::test]
      async fn test_completes_quickly() {
          let result = timeout(Duration::from_millis(100), fast_task()).await;
          assert!(result.is_ok(), "task took too long");
      }
      ```
      
      ---
      
      ## 12. Common Async Mistakes
      
      ### Block in Async Context
      
      ```rust
      // BAD: blocks the executor thread, starves other tasks
      async fn hash(password: &str) -> String {
          bcrypt::hash(password, 12).unwrap()  // CPU-intensive, blocks
      }
      
      // GOOD: offload to blocking thread pool
      async fn hash(password: String) -> String {
          tokio::task::spawn_blocking(move || {
              bcrypt::hash(&password, 12).unwrap()
          })
          .await
          .unwrap()
      }
      
      // BAD: blocking sleep
      async fn wait() {
          std::thread::sleep(Duration::from_secs(1));  // blocks executor
      }
      
      // GOOD:
      async fn wait() {
          tokio::time::sleep(Duration::from_secs(1)).await;
      }
      ```
      
      ### Hold std::Mutex Across .await
      
      ```rust
      // BAD: MutexGuard (which is not Send) held across await point
      // This will fail to compile if the future must be Send
      async fn bad_update(state: Arc<Mutex<State>>) {
          let mut guard = state.lock().unwrap();
          guard.count += 1;
          do_async_work().await;  // guard still held — not Send!
          guard.finalize();
      }
      
      // GOOD: release lock before await
      async fn good_update(state: Arc<Mutex<State>>) {
          {
              let mut guard = state.lock().unwrap();
              guard.count += 1;
          }  // guard dropped here
          do_async_work().await;
          {
              let mut guard = state.lock().unwrap();
              guard.finalize();
          }
      }
      
      // ALTERNATIVE: use tokio::sync::Mutex if you need to hold across awaits
      ```
      
      ### Missing Send Bounds
      
      ```rust
      // Task spawned with tokio::spawn must be Send + 'static
      // This fails if you capture non-Send types (like Rc, RefCell)
      let rc = Rc::new(5);
      tokio::spawn(async move {
          println!("{}", rc);  // ERROR: Rc is not Send
      });
      
      // GOOD: use Arc instead of Rc
      let arc = Arc::new(5);
      tokio::spawn(async move {
          println!("{}", arc);  // OK
      });
      ```
      
      ### Forget to Drive Futures
      
      ```rust
      // BAD: creating a future without awaiting it — nothing happens
      async fn fire_and_maybe_forget() {
          let future = send_email("user@example.com");  // not awaited, not spawned
          // future is dropped, email never sent
      }
      
      // GOOD: either await or spawn
      async fn fire_and_actually_do_it() {
          // Option 1: await (sequential)
          send_email("user@example.com").await.unwrap();
      
          // Option 2: spawn (concurrent, detached)
          tokio::spawn(async { send_email("user@example.com").await.ok() });
      }
      ```
      
      ### Unbounded Spawning
      
      ```rust
      // BAD: spawning one task per item with no limit — OOM on large input
      async fn process_all(items: Vec<Item>) {
          for item in items {
              tokio::spawn(async move { process(item).await });
          }
      }
      
      // GOOD: use JoinSet with capacity limit or a semaphore
      use tokio::sync::Semaphore;
      
      async fn process_all(items: Vec<Item>) {
          let sem = Arc::new(Semaphore::new(50));  // max 50 concurrent tasks
          let mut set = JoinSet::new();
      
          for item in items {
              let permit = sem.clone().acquire_owned().await.unwrap();
              set.spawn(async move {
                  let _permit = permit;  // released when task ends
                  process(item).await
              });
          }
      
          while set.join_next().await.is_some() {}
      }
      ```
      
    • ecosystem.md 22.7 KB
      # Rust Ecosystem Reference
      
      ## Table of Contents
      
      1. [serde Advanced](#1-serde-advanced)
      2. [clap](#2-clap)
      3. [reqwest](#3-reqwest)
      4. [sqlx](#4-sqlx)
      5. [axum](#5-axum)
      6. [tracing](#6-tracing)
      7. [rayon](#7-rayon)
      8. [itertools](#8-itertools)
      9. [Cow](#9-cow)
      
      ---
      
      ## 1. serde Advanced
      
      ### Use Custom Serialization with `serialize_with` / `deserialize_with`
      
      ```rust
      use serde::{Deserialize, Serialize};
      use chrono::{DateTime, Utc};
      
      #[derive(Serialize, Deserialize)]
      pub struct Event {
          pub name: String,
          #[serde(with = "chrono::serde::ts_seconds")]
          pub occurred_at: DateTime<Utc>,
          #[serde(
              serialize_with = "serialize_uppercase",
              deserialize_with = "deserialize_uppercase"
          )]
          pub code: String,
      }
      
      fn serialize_uppercase<S>(value: &str, s: S) -> Result<S::Ok, S::Error>
      where
          S: serde::Serializer,
      {
          s.serialize_str(&value.to_uppercase())
      }
      
      fn deserialize_uppercase<'de, D>(d: D) -> Result<String, D::Error>
      where
          D: serde::Deserializer<'de>,
      {
          let raw = String::deserialize(d)?;
          Ok(raw.to_uppercase())
      }
      ```
      
      ### Use `#[serde(with)]` for Custom Module
      
      ```rust
      mod as_base64 {
          use base64::{engine::general_purpose, Engine};
          use serde::{Deserialize, Deserializer, Serializer};
      
          pub fn serialize<S>(bytes: &[u8], s: S) -> Result<S::Ok, S::Error>
          where
              S: Serializer,
          {
              s.serialize_str(&general_purpose::STANDARD.encode(bytes))
          }
      
          pub fn deserialize<'de, D>(d: D) -> Result<Vec<u8>, D::Error>
          where
              D: Deserializer<'de>,
          {
              let s = String::deserialize(d)?;
              general_purpose::STANDARD
                  .decode(&s)
                  .map_err(serde::de::Error::custom)
          }
      }
      
      #[derive(Serialize, Deserialize)]
      pub struct Secret {
          #[serde(with = "as_base64")]
          pub key: Vec<u8>,
      }
      ```
      
      ### Flatten Nested Structs
      
      ```rust
      #[derive(Serialize, Deserialize)]
      pub struct Metadata {
          pub created_by: String,
          pub version: u32,
      }
      
      #[derive(Serialize, Deserialize)]
      pub struct Record {
          pub id: u64,
          pub name: String,
          #[serde(flatten)]
          pub meta: Metadata,
          // Serializes as: { "id": 1, "name": "...", "created_by": "...", "version": 1 }
      }
      
      // Capture unknown fields
      #[derive(Serialize, Deserialize)]
      pub struct Flexible {
          pub known: String,
          #[serde(flatten)]
          pub extra: std::collections::HashMap<String, serde_json::Value>,
      }
      ```
      
      ### Tag Enums (Internal, External, Adjacent, Untagged)
      
      ```rust
      // External (default): { "TypeName": { ...fields } }
      #[derive(Serialize, Deserialize)]
      pub enum External {
          Text { content: String },
          Number { value: i64 },
      }
      
      // Internal: { "type": "Text", "content": "..." }
      #[derive(Serialize, Deserialize)]
      #[serde(tag = "type")]
      pub enum Internal {
          Text { content: String },
          Number { value: i64 },
      }
      
      // Adjacent: { "type": "Text", "data": { "content": "..." } }
      #[derive(Serialize, Deserialize)]
      #[serde(tag = "type", content = "data")]
      pub enum Adjacent {
          Text { content: String },
          Number { value: i64 },
      }
      
      // Untagged: tries each variant until one succeeds
      #[derive(Serialize, Deserialize)]
      #[serde(untagged)]
      pub enum Untagged {
          Text { content: String },
          Number { value: i64 },
          Raw(String),
      }
      ```
      
      ### Reject Unknown Fields
      
      ```rust
      #[derive(Deserialize)]
      #[serde(deny_unknown_fields)]
      pub struct StrictConfig {
          pub host: String,
          pub port: u16,
          // Any unknown key in JSON causes deserialization to fail
      }
      ```
      
      ### Use `#[serde(remote)]` for External Types
      
      ```rust
      // For types you don't own, create a remote definition
      #[derive(Serialize, Deserialize)]
      #[serde(remote = "std::time::Duration")]
      struct DurationDef {
          secs: u64,
          nanos: u32,
      }
      
      #[derive(Serialize, Deserialize)]
      pub struct Config {
          #[serde(with = "DurationDef")]
          pub timeout: std::time::Duration,
      }
      ```
      
      ---
      
      ## 2. clap
      
      ### Define CLI with Derive API
      
      ```rust
      use clap::{Args, Parser, Subcommand, ValueEnum};
      
      #[derive(Parser)]
      #[command(name = "mytool", version, about = "A tool that does things")]
      pub struct Cli {
          #[command(subcommand)]
          pub command: Commands,
      
          /// Increase verbosity (-v, -vv, -vvv)
          #[arg(short, long, action = clap::ArgAction::Count, global = true)]
          pub verbose: u8,
      
          /// Config file path
          #[arg(long, env = "MYTOOL_CONFIG", default_value = "config.toml", global = true)]
          pub config: std::path::PathBuf,
      }
      
      #[derive(Subcommand)]
      pub enum Commands {
          /// Fetch data from the server
          Fetch(FetchArgs),
          /// Push data to the server
          Push(PushArgs),
      }
      
      #[derive(Args)]
      pub struct FetchArgs {
          /// Target URL
          #[arg(value_parser = parse_url)]
          pub url: url::Url,
      
          /// Output format
          #[arg(long, value_enum, default_value_t = OutputFormat::Json)]
          pub format: OutputFormat,
      
          /// Optional tags (can be repeated)
          #[arg(long = "tag", short = 't')]
          pub tags: Vec<String>,
      
          /// Dry run mode
          #[arg(long, conflicts_with = "output")]
          pub dry_run: bool,
      
          /// Write output to file
          #[arg(long)]
          pub output: Option<std::path::PathBuf>,
      }
      
      #[derive(ValueEnum, Clone)]
      pub enum OutputFormat {
          Json,
          Csv,
          Pretty,
      }
      
      fn parse_url(s: &str) -> Result<url::Url, String> {
          url::Url::parse(s).map_err(|e| e.to_string())
      }
      ```
      
      ### Parse and Dispatch
      
      ```rust
      fn main() {
          let cli = Cli::parse();
      
          match cli.command {
              Commands::Fetch(args) => run_fetch(args, cli.verbose),
              Commands::Push(args) => run_push(args, cli.verbose),
          }
      }
      ```
      
      ### Generate Shell Completions
      
      ```rust
      use clap::CommandFactory;
      use clap_complete::{generate, Shell};
      
      fn print_completions(shell: Shell) {
          let mut cmd = Cli::command();
          generate(shell, &mut cmd, "mytool", &mut std::io::stdout());
      }
      ```
      
      ---
      
      ## 3. reqwest
      
      ### Build a Shared Client
      
      ```rust
      use reqwest::{Client, ClientBuilder, header};
      use std::time::Duration;
      
      fn build_client(base_token: &str) -> reqwest::Result<Client> {
          let mut headers = header::HeaderMap::new();
          let auth = header::HeaderValue::from_str(&format!("Bearer {}", base_token))
              .expect("Invalid token");
          headers.insert(header::AUTHORIZATION, auth);
      
          ClientBuilder::new()
              .timeout(Duration::from_secs(30))
              .connect_timeout(Duration::from_secs(5))
              .default_headers(headers)
              .user_agent("myapp/1.0")
              .build()
      }
      ```
      
      ### Send GET / POST / PUT Requests
      
      ```rust
      use serde::{Deserialize, Serialize};
      
      #[derive(Deserialize)]
      struct ApiResponse { data: Vec<Item> }
      
      #[derive(Serialize)]
      struct CreateRequest { name: String, value: u32 }
      
      async fn fetch_items(client: &Client, url: &str) -> anyhow::Result<Vec<Item>> {
          let resp = client
              .get(url)
              .query(&[("limit", "100"), ("page", "1")])
              .send()
              .await?
              .error_for_status()?
              .json::<ApiResponse>()
              .await?;
      
          Ok(resp.data)
      }
      
      async fn create_item(client: &Client, url: &str, name: &str) -> anyhow::Result<Item> {
          let body = CreateRequest { name: name.to_string(), value: 42 };
      
          client
              .post(url)
              .json(&body)
              .send()
              .await?
              .error_for_status()?
              .json::<Item>()
              .await
              .map_err(Into::into)
      }
      ```
      
      ### Upload Multipart Form
      
      ```rust
      use reqwest::multipart;
      
      async fn upload_file(client: &Client, url: &str, path: &std::path::Path) -> anyhow::Result<()> {
          let file_bytes = tokio::fs::read(path).await?;
          let filename = path.file_name().unwrap().to_string_lossy().into_owned();
      
          let part = multipart::Part::bytes(file_bytes)
              .file_name(filename)
              .mime_str("application/octet-stream")?;
      
          let form = multipart::Form::new()
              .text("description", "my upload")
              .part("file", part);
      
          client.post(url).multipart(form).send().await?.error_for_status()?;
          Ok(())
      }
      ```
      
      ### Stream a Response
      
      ```rust
      use futures_util::StreamExt;
      
      async fn stream_download(client: &Client, url: &str) -> anyhow::Result<Vec<u8>> {
          let mut stream = client.get(url).send().await?.bytes_stream();
          let mut buf = Vec::new();
      
          while let Some(chunk) = stream.next().await {
              buf.extend_from_slice(&chunk?);
          }
      
          Ok(buf)
      }
      ```
      
      ### Retry with Exponential Backoff
      
      ```rust
      use std::time::Duration;
      
      async fn get_with_retry(client: &Client, url: &str, max: usize) -> anyhow::Result<String> {
          let mut delay = Duration::from_millis(200);
      
          for attempt in 0..max {
              match client.get(url).send().await?.error_for_status() {
                  Ok(resp) => return Ok(resp.text().await?),
                  Err(e) if attempt + 1 < max => {
                      tokio::time::sleep(delay).await;
                      delay *= 2;
                      tracing::warn!(attempt, %e, "Retrying request");
                  }
                  Err(e) => return Err(e.into()),
              }
          }
      
          unreachable!()
      }
      ```
      
      ---
      
      ## 4. sqlx
      
      ### Set Up a Connection Pool
      
      ```toml
      # Cargo.toml
      sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-rustls", "macros", "chrono", "uuid"] }
      ```
      
      ```rust
      use sqlx::PgPool;
      
      pub async fn connect(database_url: &str) -> sqlx::Result<PgPool> {
          PgPool::connect(database_url).await
      }
      
      // Or with options
      use sqlx::postgres::PgPoolOptions;
      
      pub async fn connect_pool(database_url: &str) -> sqlx::Result<PgPool> {
          PgPoolOptions::new()
              .max_connections(20)
              .acquire_timeout(std::time::Duration::from_secs(5))
              .connect(database_url)
              .await
      }
      ```
      
      ### Write Compile-Time Checked Queries
      
      ```rust
      // DATABASE_URL must be set at compile time
      // sqlx::query! checks SQL against live schema
      
      pub async fn get_user(pool: &PgPool, id: i64) -> sqlx::Result<Option<User>> {
          sqlx::query_as!(
              User,
              r#"SELECT id, name, email, created_at FROM users WHERE id = $1"#,
              id
          )
          .fetch_optional(pool)
          .await
      }
      
      pub async fn list_users(pool: &PgPool, limit: i64) -> sqlx::Result<Vec<User>> {
          sqlx::query_as!(
              User,
              r#"SELECT id, name, email, created_at FROM users ORDER BY id LIMIT $1"#,
              limit
          )
          .fetch_all(pool)
          .await
      }
      ```
      
      ### Derive `FromRow`
      
      ```rust
      use sqlx::FromRow;
      use chrono::{DateTime, Utc};
      use uuid::Uuid;
      
      #[derive(Debug, FromRow)]
      pub struct User {
          pub id: i64,
          pub name: String,
          pub email: String,
          pub created_at: DateTime<Utc>,
      }
      
      // Override column name
      #[derive(Debug, FromRow)]
      pub struct Post {
          pub id: Uuid,
          #[sqlx(rename = "body_text")]
          pub body: String,
          // Skip a column that won't be in every query
          #[sqlx(skip)]
          pub computed: Option<String>,
      }
      ```
      
      ### Use Transactions
      
      ```rust
      pub async fn transfer_funds(
          pool: &PgPool,
          from: i64,
          to: i64,
          amount: i64,
      ) -> sqlx::Result<()> {
          let mut tx = pool.begin().await?;
      
          sqlx::query!("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, from)
              .execute(&mut *tx)
              .await?;
      
          sqlx::query!("UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to)
              .execute(&mut *tx)
              .await?;
      
          tx.commit().await
      }
      ```
      
      ### Map JSON Columns
      
      ```rust
      use serde::{Deserialize, Serialize};
      use sqlx::types::Json;
      
      #[derive(Debug, Serialize, Deserialize)]
      pub struct Settings {
          pub theme: String,
          pub notifications: bool,
      }
      
      #[derive(Debug, FromRow)]
      pub struct UserWithSettings {
          pub id: i64,
          pub name: String,
          pub settings: Json<Settings>,  // maps JSON column to typed struct
      }
      
      pub async fn get_settings(pool: &PgPool, id: i64) -> sqlx::Result<Settings> {
          let row = sqlx::query_as!(
              UserWithSettings,
              r#"SELECT id, name, settings AS "settings: Json<Settings>" FROM users WHERE id = $1"#,
              id
          )
          .fetch_one(pool)
          .await?;
      
          Ok(row.settings.0)
      }
      ```
      
      ### Run Migrations
      
      ```rust
      // Migrations live in ./migrations/*.sql, named 0001_create_users.sql etc.
      pub async fn run_migrations(pool: &PgPool) -> sqlx::Result<()> {
          sqlx::migrate!("./migrations").run(pool).await
      }
      ```
      
      ---
      
      ## 5. axum
      
      ### Define a Router with State
      
      ```rust
      use axum::{Router, routing::{get, post}};
      use std::sync::Arc;
      
      #[derive(Clone)]
      pub struct AppState {
          pub pool: sqlx::PgPool,
          pub config: Arc<Config>,
      }
      
      pub fn build_router(state: AppState) -> Router {
          Router::new()
              .route("/health", get(health_handler))
              .route("/users", get(list_users).post(create_user))
              .route("/users/:id", get(get_user).delete(delete_user))
              .nest("/admin", admin_routes())
              .layer(tower_http::trace::TraceLayer::new_for_http())
              .with_state(state)
      }
      ```
      
      ### Write Handlers with Extractors
      
      ```rust
      use axum::{
          extract::{Path, Query, State, Json},
          http::StatusCode,
          response::IntoResponse,
      };
      use serde::Deserialize;
      
      #[derive(Deserialize)]
      pub struct Pagination {
          pub page: Option<u32>,
          pub limit: Option<u32>,
      }
      
      pub async fn list_users(
          State(state): State<AppState>,
          Query(params): Query<Pagination>,
      ) -> impl IntoResponse {
          let limit = params.limit.unwrap_or(20).min(100);
          let page = params.page.unwrap_or(0);
      
          match fetch_users(&state.pool, limit as i64, page as i64).await {
              Ok(users) => Json(users).into_response(),
              Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
          }
      }
      
      #[derive(Deserialize)]
      pub struct CreateUserRequest {
          pub name: String,
          pub email: String,
      }
      
      pub async fn create_user(
          State(state): State<AppState>,
          Json(body): Json<CreateUserRequest>,
      ) -> Result<(StatusCode, Json<User>), AppError> {
          let user = insert_user(&state.pool, &body.name, &body.email).await?;
          Ok((StatusCode::CREATED, Json(user)))
      }
      ```
      
      ### Define a Typed Error Response
      
      ```rust
      use axum::{http::StatusCode, response::{IntoResponse, Response}, Json};
      use serde_json::json;
      
      pub enum AppError {
          NotFound(String),
          Database(sqlx::Error),
          Validation(String),
      }
      
      impl IntoResponse for AppError {
          fn into_response(self) -> Response {
              let (status, message) = match self {
                  AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
                  AppError::Validation(msg) => (StatusCode::UNPROCESSABLE_ENTITY, msg),
                  AppError::Database(e) => {
                      tracing::error!("Database error: {}", e);
                      (StatusCode::INTERNAL_SERVER_ERROR, "Internal error".to_string())
                  }
              };
      
              (status, Json(json!({ "error": message }))).into_response()
          }
      }
      
      impl From<sqlx::Error> for AppError {
          fn from(e: sqlx::Error) -> Self {
              AppError::Database(e)
          }
      }
      ```
      
      ### Handle WebSocket Connections
      
      ```rust
      use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
      
      pub async fn ws_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
          ws.on_upgrade(handle_socket)
      }
      
      async fn handle_socket(mut socket: WebSocket) {
          while let Some(msg) = socket.recv().await {
              match msg {
                  Ok(Message::Text(text)) => {
                      if socket.send(Message::Text(format!("echo: {text}"))).await.is_err() {
                          break;
                      }
                  }
                  Ok(Message::Close(_)) | Err(_) => break,
                  _ => {}
              }
          }
      }
      ```
      
      ### Gracefully Shut Down the Server
      
      ```rust
      use tokio::net::TcpListener;
      
      pub async fn serve(router: Router) -> anyhow::Result<()> {
          let listener = TcpListener::bind("0.0.0.0:3000").await?;
      
          axum::serve(listener, router)
              .with_graceful_shutdown(shutdown_signal())
              .await?;
      
          Ok(())
      }
      
      async fn shutdown_signal() {
          tokio::signal::ctrl_c().await.expect("Failed to install Ctrl+C handler");
          tracing::info!("Shutdown signal received");
      }
      ```
      
      ---
      
      ## 6. tracing
      
      ### Set Up a Subscriber
      
      ```rust
      use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
      
      pub fn init_tracing() {
          tracing_subscriber::registry()
              .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()))
              .with(tracing_subscriber::fmt::layer().with_target(true))
              .init();
      }
      
      // For JSON output (structured logging in prod)
      pub fn init_json_tracing() {
          tracing_subscriber::registry()
              .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()))
              .with(tracing_subscriber::fmt::layer().json())
              .init();
      }
      ```
      
      ### Instrument Functions
      
      ```rust
      use tracing::{instrument, info, warn, error, debug, Span};
      
      #[instrument(skip(pool), fields(user_id = %id))]
      pub async fn get_user_profile(pool: &PgPool, id: i64) -> anyhow::Result<Profile> {
          debug!("Fetching user profile");
      
          let profile = sqlx::query_as!(Profile, "SELECT * FROM profiles WHERE user_id = $1", id)
              .fetch_optional(pool)
              .await?
              .ok_or_else(|| anyhow::anyhow!("Profile not found"))?;
      
          info!(email = %profile.email, "Profile fetched");
          Ok(profile)
      }
      ```
      
      ### Create and Enter Spans Manually
      
      ```rust
      use tracing::{info_span, Instrument};
      
      pub async fn process_batch(items: Vec<Item>) {
          for item in items {
              let span = info_span!("process_item", item_id = %item.id, kind = %item.kind);
      
              async move {
                  tracing::info!("Processing item");
                  // work...
                  tracing::info!("Item done");
              }
              .instrument(span)
              .await;
          }
      }
      ```
      
      ### Add Structured Fields to Events
      
      ```rust
      use tracing::info;
      
      pub fn log_request(method: &str, path: &str, status: u16, latency_ms: u64) {
          info!(
              http.method = method,
              http.path = path,
              http.status = status,
              latency_ms = latency_ms,
              "Request completed"
          );
      }
      ```
      
      ### Filter by Directive
      
      ```
      # Environment variable examples
      RUST_LOG=info
      RUST_LOG=myapp=debug,sqlx=warn,tower_http=info
      RUST_LOG=debug,hyper=off
      ```
      
      ---
      
      ## 7. rayon
      
      ### Parallelize with `par_iter`
      
      ```rust
      use rayon::prelude::*;
      
      fn sum_squares(numbers: &[f64]) -> f64 {
          numbers.par_iter().map(|x| x * x).sum()
      }
      
      fn find_primes(limit: u64) -> Vec<u64> {
          (2..limit)
              .into_par_iter()
              .filter(|&n| is_prime(n))
              .collect()
      }
      
      fn transform_records(records: Vec<Record>) -> Vec<Output> {
          records.into_par_iter().map(process_record).collect()
      }
      ```
      
      ### Build a Custom Thread Pool
      
      ```rust
      use rayon::ThreadPoolBuilder;
      
      fn main() {
          let pool = ThreadPoolBuilder::new()
              .num_threads(4)
              .thread_name(|i| format!("worker-{}", i))
              .build()
              .unwrap();
      
          pool.install(|| {
              (0..1000).into_par_iter().for_each(|i| {
                  println!("Processing {}", i);
              });
          });
      }
      ```
      
      ### Bridge Sequential Iterators
      
      ```rust
      use rayon::iter::ParallelBridge;
      
      fn process_lines(reader: impl std::io::BufRead + Send) -> Vec<String> {
          reader
              .lines()
              .par_bridge()           // converts Iterator -> ParallelIterator
              .filter_map(|l| l.ok())
              .map(|l| l.trim().to_string())
              .collect()
      }
      ```
      
      ### Know When to Choose rayon vs tokio
      
      | Situation | Use |
      |-----------|-----|
      | CPU-bound work (parsing, compression, crypto) | `rayon` |
      | I/O-bound work (network, disk, database) | `tokio` |
      | Mix: CPU work inside async | `tokio::task::spawn_blocking` with rayon inside |
      | Parallel collection transforms | `rayon` |
      | Concurrent HTTP requests | `tokio` + `FuturesUnordered` |
      
      ```rust
      // Offload rayon work from async context
      pub async fn heavy_computation(data: Vec<u8>) -> Vec<u8> {
          tokio::task::spawn_blocking(move || {
              data.par_iter().map(|b| b.wrapping_add(1)).collect()
          })
          .await
          .expect("blocking task panicked")
      }
      ```
      
      ---
      
      ## 8. itertools
      
      ```toml
      itertools = "0.12"
      ```
      
      ### Use Useful Combinators
      
      ```rust
      use itertools::Itertools;
      
      fn demonstrate_itertools() {
          let nums = vec![1, 2, 3, 4, 5, 6];
      
          // chunks - fixed-size non-overlapping groups
          for chunk in &nums.iter().chunks(2) {
              let v: Vec<_> = chunk.collect();
              println!("{:?}", v);  // [1,2], [3,4], [5,6]
          }
      
          // tuple_windows - sliding window of tuples
          let pairs: Vec<_> = nums.iter().tuple_windows::<(_, _)>().collect();
          // [(1,2), (2,3), (3,4), (4,5), (5,6)]
      
          // group_by - consecutive runs (like Unix uniq)
          let words = vec!["a", "a", "b", "b", "b", "c"];
          for (key, group) in &words.iter().group_by(|w| *w) {
              println!("{}: {}", key, group.count());
          }
      
          // join - format with separator (no trailing sep)
          let s = ["foo", "bar", "baz"].iter().join(", ");
          // "foo, bar, baz"
      
          // sorted_by - sort without mutating
          let sorted = nums.iter().sorted_by(|a, b| b.cmp(a)).collect_vec();
      
          // dedup - remove consecutive duplicates
          let deduped: Vec<_> = vec![1, 1, 2, 3, 3, 3, 4].into_iter().dedup().collect();
          // [1, 2, 3, 4]
      
          // interleave - alternating elements
          let a = vec![1, 3, 5];
          let b = vec![2, 4, 6];
          let merged: Vec<_> = a.into_iter().interleave(b.into_iter()).collect();
          // [1, 2, 3, 4, 5, 6]
      
          // unique - deduplicate (not just consecutive)
          let unique: Vec<_> = vec![1, 2, 1, 3, 2, 4].into_iter().unique().collect();
          // [1, 2, 3, 4]
      
          // combinations and permutations
          let combos: Vec<_> = (0..4).combinations(2).collect();
          // [[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]]
      
          // partition_map - split into two collections
          let (evens, odds): (Vec<_>, Vec<_>) =
              nums.iter().partition_map(|&n| {
                  if n % 2 == 0 { itertools::Either::Left(n) } else { itertools::Either::Right(n) }
              });
      }
      ```
      
      ---
      
      ## 9. Cow
      
      ### Understand Clone-on-Write
      
      `Cow<'a, B>` is either `Borrowed(&'a B)` or `Owned(B::Owned)`. Derefs to `&B` in both cases. Allocates only when you need to mutate.
      
      ### Use Cow in Function Signatures
      
      ```rust
      use std::borrow::Cow;
      
      // Accepts both &str and String, returns without allocating if no change needed
      fn normalize(input: &str) -> Cow<str> {
          if input.chars().all(|c| c.is_lowercase()) {
              Cow::Borrowed(input)        // zero allocation
          } else {
              Cow::Owned(input.to_lowercase())  // allocates only when needed
          }
      }
      
      // Accept Cow to handle both owned and borrowed callers
      fn process(name: Cow<str>) {
          println!("Processing: {}", name);
      }
      
      // Call sites
      process(Cow::Borrowed("hello"));
      process(Cow::Owned(String::from("world")));
      process(normalize("Mixed"));
      ```
      
      ### Enable Zero-Copy Parsing
      
      ```rust
      use serde::Deserialize;
      
      // serde can borrow from input bytes when possible
      #[derive(Deserialize)]
      pub struct Request<'a> {
          #[serde(borrow)]
          pub method: Cow<'a, str>,  // borrows from JSON bytes if no escaping needed
          pub id: u64,
      }
      ```
      
      ### Build Efficient Builders with Cow
      
      ```rust
      use std::borrow::Cow;
      
      pub struct Query<'a> {
          table: Cow<'a, str>,
          conditions: Vec<Cow<'a, str>>,
      }
      
      impl<'a> Query<'a> {
          pub fn from_table(table: impl Into<Cow<'a, str>>) -> Self {
              Query { table: table.into(), conditions: vec![] }
          }
      
          pub fn where_clause(mut self, cond: impl Into<Cow<'a, str>>) -> Self {
              self.conditions.push(cond.into());
              self
          }
      
          pub fn build(&self) -> String {
              if self.conditions.is_empty() {
                  format!("SELECT * FROM {}", self.table)
              } else {
                  format!("SELECT * FROM {} WHERE {}", self.table, self.conditions.iter().join(" AND "))
              }
          }
      }
      ```
      
    • error-handling.md 15 KB
      # Rust Error Handling Reference
      
      ## Table of Contents
      
      1. [Result and Option](#1-result-and-option)
      2. [The ? Operator](#2-the--operator)
      3. [thiserror](#3-thiserror)
      4. [anyhow](#4-anyhow)
      5. [Custom Error Enums](#5-custom-error-enums)
      6. [Error Conversion](#6-error-conversion)
      7. [Error Context](#7-error-context)
      8. [panic vs Result](#8-panic-vs-result)
      9. [Result in main](#9-result-in-main)
      10. [Anti-Patterns](#10-anti-patterns)
      
      ---
      
      ## 1. Result and Option
      
      ### Basics
      
      ```rust
      // Result<T, E>: Ok(T) on success, Err(E) on failure
      fn parse_port(s: &str) -> Result<u16, std::num::ParseIntError> {
          s.parse::<u16>()
      }
      
      // Option<T>: Some(T) or None
      fn find_user(id: u64) -> Option<User> {
          users.get(&id).cloned()
      }
      ```
      
      ### map and and_then
      
      ```rust
      // map: transform Ok/Some without touching Err/None
      let doubled: Option<i32> = Some(5).map(|x| x * 2);          // Some(10)
      let upper: Result<String, _> = Ok("hi").map(|s: &str| s.to_uppercase());
      
      // and_then: chain fallible operations (flatMap)
      fn load_config(path: &str) -> Result<Config, Error> {
          read_file(path)
              .and_then(|contents| parse_toml(&contents))
              .and_then(|raw| validate_config(raw))
      }
      
      // Option::and_then for chaining lookups
      let city = get_user(id)
          .and_then(|user| get_address(user.address_id))
          .and_then(|addr| addr.city);
      ```
      
      ### unwrap_or and unwrap_or_else
      
      ```rust
      // unwrap_or: provide a fallback value (evaluated eagerly)
      let port: u16 = parse_port(s).unwrap_or(8080);
      let name: String = maybe_name.unwrap_or_else(String::new);
      
      // unwrap_or_else: provide a closure (evaluated lazily — prefer for expensive defaults)
      let config = load_config("app.toml")
          .unwrap_or_else(|_| Config::default());
      
      // unwrap_or_default: use the Default impl
      let value: Vec<u8> = maybe_bytes.unwrap_or_default();
      ```
      
      ### ok_or and transpose
      
      ```rust
      // ok_or: convert Option into Result
      let user = find_user(id).ok_or(Error::UserNotFound(id))?;
      
      // ok_or_else: lazy version
      let user = find_user(id)
          .ok_or_else(|| Error::UserNotFound(id))?;
      
      // transpose: flip Option<Result<T, E>> <-> Result<Option<T>, E>
      let maybe_result: Option<Result<u32, _>> = Some("42".parse());
      let result_maybe: Result<Option<u32>, _> = maybe_result.transpose();  // Ok(Some(42))
      
      // Useful in iterators when you want the first error or all-None
      let parsed: Result<Vec<u32>, _> = strings
          .iter()
          .map(|s| s.parse::<u32>())
          .collect();  // fails on first parse error
      ```
      
      ### Other Useful Methods
      
      ```rust
      // is_ok, is_err, is_some, is_none
      if result.is_err() { log_failure(); }
      
      // map_err: transform only the error type
      let result = op().map_err(|e| format!("Operation failed: {e}"));
      
      // or / or_else: provide alternative on failure
      let result = primary().or_else(|_| fallback());
      
      // inspect / inspect_err: side effects without consuming
      let result = load().inspect(|v| tracing::debug!(?v, "loaded"))
                         .inspect_err(|e| tracing::warn!(?e, "load failed"));
      
      // flatten: Option<Option<T>> -> Option<T>, Result<Result<T,E>,E> -> Result<T,E>
      let flat: Option<i32> = Some(Some(5)).flatten();  // Some(5)
      ```
      
      ---
      
      ## 2. The ? Operator
      
      ### Result Propagation
      
      ```rust
      // ? desugars to: match on Err, call From::from on the error, return early
      fn read_config(path: &str) -> Result<Config, AppError> {
          let text = std::fs::read_to_string(path)?;  // io::Error -> AppError via From
          let config: Config = toml::from_str(&text)?; // toml::Error -> AppError via From
          Ok(config)
      }
      ```
      
      ### Option Propagation
      
      ```rust
      // ? on Option returns None immediately (requires the function to return Option)
      fn first_line_word(text: &str) -> Option<&str> {
          text.lines().next()?.split_whitespace().next()
      }
      
      // Cannot mix Option? and Result? in the same function without conversion
      // Use .ok_or() or .ok_or_else() to convert Option -> Result
      fn find_section(text: &str) -> Result<&str, AppError> {
          text.lines()
              .find(|l| l.starts_with('['))
              .ok_or(AppError::NoSection)?
              .trim()
              .into()
      }
      ```
      
      ### From Conversion
      
      ```rust
      // ? calls From::from automatically. Define From impls to unlock ?
      impl From<std::io::Error> for AppError {
          fn from(e: std::io::Error) -> Self {
              AppError::Io(e)
          }
      }
      
      // Now io::Error can be converted with ?
      fn write_output(data: &[u8]) -> Result<(), AppError> {
          std::fs::write("out.bin", data)?;  // io::Error converted automatically
          Ok(())
      }
      ```
      
      ### Early Return Pattern
      
      ```rust
      // ? enables clean early-return without match chains
      fn process(input: &str) -> Result<Output, AppError> {
          let parsed = parse(input)?;
          let validated = validate(parsed)?;
          let enriched = enrich(validated)?;
          Ok(transform(enriched))
      }
      ```
      
      ---
      
      ## 3. thiserror
      
      thiserror generates `std::error::Error` impls via derive macros. Use it in **libraries**.
      
      ### Derive Error and Format Messages
      
      ```rust
      use thiserror::Error;
      
      #[derive(Debug, Error)]
      pub enum AppError {
          #[error("user {id} not found")]
          UserNotFound { id: u64 },
      
          #[error("invalid email address: {0}")]
          InvalidEmail(String),
      
          #[error("timeout after {0:?}")]
          Timeout(std::time::Duration),
      
          #[error("internal error")]
          Internal,
      }
      ```
      
      ### #[from] for Automatic Conversion
      
      ```rust
      #[derive(Debug, Error)]
      pub enum AppError {
          // #[from] generates From<io::Error> for AppError
          #[error("IO error: {0}")]
          Io(#[from] std::io::Error),
      
          // Enables ? on sqlx operations automatically
          #[error("database error: {0}")]
          Database(#[from] sqlx::Error),
      
          #[error("serialization error: {0}")]
          Json(#[from] serde_json::Error),
      }
      ```
      
      ### #[source] for Error Chains
      
      ```rust
      #[derive(Debug, Error)]
      pub enum AppError {
          // #[source] exposes inner error via Error::source()
          // #[from] implies #[source] automatically
          #[error("config load failed")]
          Config {
              #[source]
              cause: std::io::Error,
          },
      
          // transparent: delegate Display and source to inner error
          #[error(transparent)]
          Other(#[from] anyhow::Error),
      }
      ```
      
      ### Struct Errors with thiserror
      
      ```rust
      #[derive(Debug, Error)]
      #[error("parse failed at line {line}: {message}")]
      pub struct ParseError {
          pub line: usize,
          pub message: String,
          #[source]
          pub cause: Option<std::num::ParseIntError>,
      }
      ```
      
      ---
      
      ## 4. anyhow
      
      anyhow provides a single opaque error type for **application** (binary) code.
      
      ### anyhow::Result and anyhow!()
      
      ```rust
      use anyhow::{anyhow, bail, ensure, Context, Result};
      
      fn load(path: &str) -> Result<Config> {
          let text = std::fs::read_to_string(path)?;  // any error works with ?
          let config = serde_json::from_str(&text)?;
          Ok(config)
      }
      
      // anyhow!() creates an ad-hoc error
      fn validate(n: i32) -> Result<i32> {
          if n < 0 {
              return Err(anyhow!("expected non-negative, got {n}"));
          }
          Ok(n)
      }
      ```
      
      ### bail! and ensure!
      
      ```rust
      fn process(value: i32) -> Result<()> {
          // bail!() is return Err(anyhow!(...))
          if value > 1000 {
              bail!("value {value} exceeds maximum of 1000");
          }
      
          // ensure!() is if !condition { bail!(...) }
          ensure!(value >= 0, "value must be non-negative, got {value}");
      
          Ok(())
      }
      ```
      
      ### .context() and .with_context()
      
      ```rust
      fn init() -> Result<()> {
          let config = std::fs::read_to_string("config.toml")
              .context("failed to read config.toml")?;
      
          // with_context: lazy, use when message is expensive to build
          let parsed: Config = toml::from_str(&config)
              .with_context(|| format!("failed to parse config (len={})", config.len()))?;
      
          Ok(())
      }
      ```
      
      ### Downcasting
      
      ```rust
      fn handle(err: anyhow::Error) {
          // Check if the underlying error is a specific type
          if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
              eprintln!("IO error: {io_err}");
          } else {
              eprintln!("Unknown error: {err:#}");
          }
      }
      
      // {:#} prints the full error chain
      // {:?} prints the debug representation including backtrace
      ```
      
      ---
      
      ## 5. Custom Error Enums
      
      ### Design Error Hierarchies
      
      ```rust
      // Top-level public error: coarse-grained, stable API surface
      #[derive(Debug, Error)]
      pub enum ServiceError {
          #[error("authentication failed")]
          Auth(#[from] AuthError),
      
          #[error("database unavailable")]
          Database(#[from] DbError),
      
          #[error("request invalid: {0}")]
          Validation(String),
      }
      
      // Sub-module error: fine-grained, internal
      #[derive(Debug, Error)]
      pub enum AuthError {
          #[error("token expired")]
          TokenExpired,
      
          #[error("invalid signature")]
          BadSignature,
      
          #[error("user {0} locked")]
          AccountLocked(u64),
      }
      ```
      
      ### When to Split vs Combine
      
      ```rust
      // SPLIT when:
      // - Callers need to pattern-match specific variants
      // - Different modules own different error domains
      // - You want stable public API with internal flexibility
      
      // COMBINE (single enum) when:
      // - Small codebase with few error kinds
      // - Errors don't need distinct handling by callers
      // - Internal-only code
      
      // Guideline: one error enum per public API boundary (crate, module, trait)
      ```
      
      ---
      
      ## 6. Error Conversion
      
      ### impl From
      
      ```rust
      impl From<std::io::Error> for AppError {
          fn from(e: std::io::Error) -> Self {
              match e.kind() {
                  std::io::ErrorKind::NotFound => AppError::NotFound,
                  std::io::ErrorKind::PermissionDenied => AppError::Forbidden,
                  _ => AppError::Io(e),
              }
          }
      }
      ```
      
      ### Manual Conversion
      
      ```rust
      // When From is too broad, convert explicitly with map_err
      fn read_key(path: &str) -> Result<Vec<u8>, AppError> {
          std::fs::read(path).map_err(|e| {
              if e.kind() == std::io::ErrorKind::NotFound {
                  AppError::KeyMissing(path.to_string())
              } else {
                  AppError::Io(e)
              }
          })
      }
      ```
      
      ### Converting Between Error Crates
      
      ```rust
      // thiserror library error -> anyhow application error: just use ?
      // anyhow error -> thiserror: use #[error(transparent)] or explicit wrapping
      
      #[derive(Debug, Error)]
      pub enum AppError {
          #[error(transparent)]
          Internal(#[from] anyhow::Error),
      }
      
      // Or convert with a helper
      fn wrap(e: anyhow::Error) -> AppError {
          AppError::Internal(e)
      }
      ```
      
      ---
      
      ## 7. Error Context
      
      ### Add Context Without Losing Source
      
      ```rust
      // anyhow .context() preserves the original error as source
      let data = fetch(url).context("failed to fetch user data")?;
      
      // thiserror: wrap in a variant with #[source]
      #[derive(Debug, Error)]
      pub enum LoadError {
          #[error("failed to read {path}")]
          Read {
              path: String,
              #[source]
              cause: std::io::Error,
          },
      }
      
      fn load(path: &str) -> Result<Vec<u8>, LoadError> {
          std::fs::read(path).map_err(|cause| LoadError::Read {
              path: path.to_string(),
              cause,
          })
      }
      ```
      
      ### Wrapping Strategy
      
      ```rust
      // Layer context at each boundary crossing
      // 1. Low-level: return raw errors with thiserror
      // 2. Service layer: add domain context with .context()
      // 3. Handler/main: print full chain with {:#}
      
      fn read_user(id: u64) -> Result<User> {
          let row = db.query_one(id)
              .with_context(|| format!("db lookup failed for user {id}"))?;
          parse_user(row)
              .with_context(|| format!("failed to parse user {id} from db row"))
      }
      ```
      
      ---
      
      ## 8. panic vs Result
      
      ### When panic Is Legitimate
      
      ```rust
      // 1. Tests: use assert!, assert_eq!, unwrap() freely
      #[test]
      fn test_parse() {
          assert_eq!(parse("42").unwrap(), 42);
      }
      
      // 2. Initialization that cannot recover
      fn main() {
          let config = load_config().expect("failed to load required config");
      }
      
      // 3. Invariant violations that indicate a programmer bug
      fn get_first(v: &[i32]) -> i32 {
          // Caller contract: v must not be empty
          v[0]  // panics on empty — that is correct behaviour
      }
      
      // 4. Prototype / throwaway code (use todo!, unimplemented!)
      fn not_implemented_yet() -> String {
          todo!("implement serialization")
      }
      ```
      
      ### catch_unwind for Panic Isolation
      
      ```rust
      use std::panic;
      
      // Catch panics from untrusted code (plugin, FFI boundary)
      let result = panic::catch_unwind(|| {
          potentially_panicking_code()
      });
      
      match result {
          Ok(value) => println!("success: {value:?}"),
          Err(_) => eprintln!("caught a panic"),
      }
      
      // Note: catch_unwind does NOT catch abort-mode panics or stack overflows
      ```
      
      ---
      
      ## 9. Result in main
      
      ### Return Result from main
      
      ```rust
      // main can return Result<(), E> where E: Debug
      fn main() -> Result<(), Box<dyn std::error::Error>> {
          let config = load_config()?;
          run(config)?;
          Ok(())
      }
      
      // With anyhow for full error chains
      fn main() -> anyhow::Result<()> {
          let config = load_config().context("startup failed")?;
          run(config)?;
          Ok(())
      }
      ```
      
      ### ExitCode and process::exit
      
      ```rust
      use std::process::ExitCode;
      
      fn main() -> ExitCode {
          match run() {
              Ok(()) => ExitCode::SUCCESS,
              Err(e) => {
                  eprintln!("error: {e:#}");
                  ExitCode::FAILURE
              }
          }
      }
      
      // process::exit for immediate termination (skips destructors)
      fn must_succeed() {
          if let Err(e) = critical_setup() {
              eprintln!("fatal: {e}");
              std::process::exit(1);
          }
      }
      ```
      
      ### Termination Trait
      
      ```rust
      // For custom exit codes beyond 0/1
      use std::process::{ExitCode, Termination};
      
      struct AppExit(u8);
      
      impl Termination for AppExit {
          fn report(self) -> ExitCode {
              ExitCode::from(self.0)
          }
      }
      
      fn main() -> AppExit {
          match run() {
              Ok(()) => AppExit(0),
              Err(AppError::ConfigMissing) => AppExit(2),
              Err(_) => AppExit(1),
          }
      }
      ```
      
      ---
      
      ## 10. Anti-Patterns
      
      ### .unwrap() Everywhere
      
      ```rust
      // BAD: panics on any error in production
      let text = std::fs::read_to_string("config.toml").unwrap();
      let user = find_user(id).unwrap();
      
      // GOOD: propagate with ?, provide defaults, or handle explicitly
      let text = std::fs::read_to_string("config.toml")
          .context("config.toml is required")?;
      let user = find_user(id).ok_or(AppError::UserNotFound(id))?;
      ```
      
      ### Stringly Typed Errors
      
      ```rust
      // BAD: callers cannot inspect or match on error kind
      fn load(path: &str) -> Result<Data, String> {
          std::fs::read_to_string(path).map_err(|e| e.to_string())
      }
      
      // GOOD: typed errors callers can handle
      fn load(path: &str) -> Result<Data, AppError> {
          let text = std::fs::read_to_string(path)?;
          Ok(parse(&text)?)
      }
      ```
      
      ### Excessive Error Types
      
      ```rust
      // BAD: one error type per function — impossible to use
      fn read_name() -> Result<String, ReadNameError> { ... }
      fn parse_age() -> Result<u32, ParseAgeError> { ... }
      fn validate() -> Result<(), ValidateError> { ... }
      
      // GOOD: one error type per domain boundary
      fn load_user(id: u64) -> Result<User, UserError> { ... }
      ```
      
      ### Ignoring Errors with let _ =
      
      ```rust
      // BAD: silently discards errors — hides bugs
      let _ = send_notification(user);
      let _ = std::fs::remove_file(tmp);
      
      // GOOD: log or explicitly decide to ignore
      if let Err(e) = send_notification(user) {
          tracing::warn!(?e, "notification failed, continuing");
      }
      
      // If truly safe to ignore, be explicit about why
      std::fs::remove_file(tmp).ok();  // .ok() signals intentional ignore
      ```
      
      ### Boxing Without Cause
      
      ```rust
      // BAD: loses type information, callers can't downcast easily
      fn run() -> Result<(), Box<dyn std::error::Error>> { ... }
      
      // GOOD in main / test harnesses, BAD in library APIs
      // For libraries, use typed errors via thiserror
      // For applications, use anyhow::Result
      ```
      
    • ownership-lifetimes.md 16.3 KB
      # Ownership and Lifetimes Reference
      
      ## Table of Contents
      
      1. [Move Semantics](#1-move-semantics)
      2. [Borrowing Rules](#2-borrowing-rules)
      3. [Lifetime Annotations](#3-lifetime-annotations)
      4. [Lifetime Elision Rules](#4-lifetime-elision-rules)
      5. [static Lifetime](#5-static-lifetime)
      6. [Interior Mutability](#6-interior-mutability)
      7. [Common Borrow Checker Patterns](#7-common-borrow-checker-patterns)
      8. [NLL (Non-Lexical Lifetimes)](#8-nll-non-lexical-lifetimes)
      9. [Self-Referential Structs](#9-self-referential-structs)
      
      ---
      
      ## 1. Move Semantics
      
      ### Understand What Moves vs What Copies
      
      Types that implement `Copy` are implicitly duplicated on assignment. All others are moved.
      
      **Copy types:** All integer primitives, `f32`/`f64`, `bool`, `char`, raw pointers, references (`&T`), arrays of Copy types, tuples of Copy types.
      
      **Move types:** `String`, `Vec<T>`, `Box<T>`, `HashMap`, any struct containing a move type.
      
      ```rust
      // Copy - both variables remain valid
      let x: i32 = 5;
      let y = x;
      println!("{} {}", x, y); // OK
      
      // Move - s1 is no longer valid after assignment
      let s1 = String::from("hello");
      let s2 = s1;
      // println!("{}", s1); // ERROR: value moved
      
      // Clone to keep both
      let s3 = String::from("hello");
      let s4 = s3.clone();
      println!("{} {}", s3, s4); // OK
      ```
      
      ### Recognize Moves in Function Calls
      
      Passing a move type to a function moves ownership into that function. The caller loses access.
      
      ```rust
      fn consume(s: String) {
          println!("{}", s);
      } // s is dropped here
      
      fn borrow(s: &String) {
          println!("{}", s);
      } // s is NOT dropped; caller retains ownership
      
      fn main() {
          let s = String::from("hello");
          borrow(&s);   // s still valid
          consume(s);   // s moved into consume
          // consume(s);  // ERROR: s already moved
      }
      ```
      
      ### Handle Moves in Closures
      
      Closures capture variables by the minimum required (reference, mutable reference, or move). Use `move` to force ownership transfer.
      
      ```rust
      let s = String::from("hello");
      
      // Closure borrows s by reference (default when possible)
      let print = || println!("{}", s);
      print();
      println!("{}", s); // s still valid
      
      // Force move into closure (required for threads)
      let s2 = String::from("world");
      let owned = move || println!("{}", s2);
      // println!("{}", s2); // ERROR: s2 moved into closure
      owned();
      ```
      
      Closures sent to threads must own their data because the thread may outlive the caller's stack:
      
      ```rust
      let data = vec![1, 2, 3];
      std::thread::spawn(move || {
          println!("{:?}", data); // data moved into thread
      });
      ```
      
      ### Avoid Moves in Loops
      
      Moving a value inside a loop consumes it on the first iteration. Use references or `clone` strategically.
      
      ```rust
      let items = vec![String::from("a"), String::from("b")];
      
      // BAD: moves items on first iteration if iterating by value
      // for item in items { ... } // items consumed after loop
      
      // GOOD: iterate by reference
      for item in &items {
          println!("{}", item);
      }
      println!("{:?}", items); // items still valid
      
      // GOOD: when you need ownership, iterate by value and handle each
      for item in items {
          process(item); // each item moved individually, that's fine
      }
      ```
      
      ---
      
      ## 2. Borrowing Rules
      
      ### Apply the Core Rules
      
      1. At any point, you may have either one `&mut T` or any number of `&T` references — never both simultaneously.
      2. References must always point to valid data (no dangling references).
      
      ```rust
      let mut s = String::from("hello");
      
      let r1 = &s;
      let r2 = &s;
      // let r3 = &mut s; // ERROR: cannot borrow as mutable while borrowed as immutable
      println!("{} {}", r1, r2);
      // r1 and r2 go out of scope here (NLL)
      
      let r3 = &mut s; // OK now
      r3.push_str("!");
      ```
      
      ### Understand Reborrowing
      
      A `&mut T` can be "reborrowed" as `&T` or a shorter-lived `&mut T`. The compiler inserts reborrows automatically in most cases.
      
      ```rust
      fn modify(s: &mut String) {
          // Reborrow: passing &mut *s creates a new &mut with shorter lifetime
          takes_str(&*s);    // reborrow as &str
          s.push_str("!"); // original mutable ref still usable after reborrow ends
      }
      
      fn takes_str(s: &str) {
          println!("{}", s);
      }
      ```
      
      ### Recognize Temporary Borrows
      
      Method calls that return references extend the borrow of `self` for the duration the reference is held.
      
      ```rust
      let mut map: HashMap<&str, Vec<i32>> = HashMap::new();
      map.insert("key", vec![1, 2, 3]);
      
      // This holds an immutable borrow of map via get()
      let val = map.get("key").unwrap();
      println!("{:?}", val);
      // val borrow ends here
      
      map.insert("other", vec![4]); // OK: no active borrows
      ```
      
      ---
      
      ## 3. Lifetime Annotations
      
      ### Read Lifetime Syntax
      
      Lifetime parameters start with `'` and appear in angle brackets. They describe relationships, not durations.
      
      ```rust
      // 'a is a generic lifetime parameter
      fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
          if x.len() > y.len() { x } else { y }
      }
      ```
      
      The annotation says: "the returned reference lives at least as long as the shorter of x and y."
      
      ### Annotate Function Lifetimes
      
      Only annotate when the compiler cannot infer the relationship (multiple input references, output borrows from one of them).
      
      ```rust
      // Input and output tied to first argument only
      fn first_word<'a>(s: &'a str) -> &'a str {
          s.split_whitespace().next().unwrap_or("")
      }
      
      // Two unrelated input lifetimes
      fn split_at<'a, 'b>(s: &'a str, _sep: &'b str) -> (&'a str, &'a str) {
          let mid = s.len() / 2;
          (&s[..mid], &s[mid..])
      }
      
      // Output may come from either input - must unify lifetimes
      fn pick<'a>(a: &'a str, b: &'a str, use_a: bool) -> &'a str {
          if use_a { a } else { b }
      }
      ```
      
      ### Annotate Struct Lifetimes
      
      Structs holding references must declare the lifetime of those references.
      
      ```rust
      struct Excerpt<'a> {
          text: &'a str,
      }
      
      impl<'a> Excerpt<'a> {
          // &self lifetime elided (elision rule 3)
          fn content(&self) -> &str {
              self.text
          }
      
          // Must annotate: output could be self.text or announcement
          fn announce<'b>(&'a self, announcement: &'b str) -> &'a str {
              println!("{}", announcement);
              self.text
          }
      }
      ```
      
      ### Use Multiple Lifetime Parameters
      
      Use multiple parameters when outputs have different source lifetimes.
      
      ```rust
      struct Cache<'data, 'key> {
          data: &'data [u8],
          key: &'key str,
      }
      
      // 'long outlives 'short: items from 'long can be stored where 'short is needed
      fn merge<'long: 'short, 'short>(
          primary: &'long str,
          fallback: &'short str,
          use_primary: bool,
      ) -> &'short str {
          if use_primary { primary } else { fallback }
      }
      ```
      
      ---
      
      ## 4. Lifetime Elision Rules
      
      ### Apply the Three Elision Rules
      
      The compiler applies these rules in order before requiring annotations:
      
      **Rule 1:** Each reference parameter gets its own distinct lifetime.
      ```rust
      fn foo(x: &str, y: &str) -> &str
      // becomes:
      fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &??? str
      // output lifetime unknown - annotation required
      ```
      
      **Rule 2:** If there is exactly one input lifetime, it applies to all outputs.
      ```rust
      fn first_word(s: &str) -> &str
      // becomes:
      fn first_word<'a>(s: &'a str) -> &'a str // inferred
      ```
      
      **Rule 3:** If one of the inputs is `&self` or `&mut self`, the output gets self's lifetime.
      ```rust
      impl Foo {
          fn bar(&self, x: &str) -> &str
          // becomes:
          fn bar<'a, 'b>(&'a self, x: &'b str) -> &'a str // inferred
      }
      ```
      
      ### Know When to Annotate
      
      Annotate when:
      - Multiple input references and the output could come from more than one of them
      - A struct holds a reference
      - You need to express a lifetime bound (`T: 'a`)
      
      Omit when:
      - Single input reference (rule 2 applies)
      - Method returning reference derived from `&self` (rule 3 applies)
      - Output is an owned type (no lifetime needed)
      
      ---
      
      ## 5. 'static Lifetime
      
      ### Understand String Literals vs Owned Data
      
      `&'static str` means the reference points to data embedded in the binary — always valid.
      
      ```rust
      let s: &'static str = "I am in the binary"; // string literal
      
      // Owned String is NOT 'static, but can produce &str with any lifetime
      let owned = String::from("dynamic");
      let borrowed: &str = &owned; // lifetime tied to owned, not 'static
      ```
      
      ### Correct the T: 'static Misconception
      
      `T: 'static` does NOT mean T lives forever. It means T contains no non-static references — T may be dropped at any time.
      
      ```rust
      // T: 'static = T owns all its data (no borrowed references inside)
      fn store<T: 'static>(val: T) {
          std::thread::spawn(move || drop(val)); // safe: T outlives any borrow
      }
      
      store(String::from("owned")); // OK: String owns its data
      store(42i32);                 // OK: Copy type, no references
      
      let s = String::from("temp");
      // store(&s); // ERROR: &s has lifetime tied to s, not 'static
      ```
      
      ### Use 'static in Error and Trait Objects
      
      Error types commonly require `'static` so they can be sent across threads or stored.
      
      ```rust
      fn might_fail() -> Result<(), Box<dyn std::error::Error + 'static>> {
          std::fs::read_to_string("missing.txt")?;
          Ok(())
      }
      
      // Thread-sendable trait object
      fn run_task(task: Box<dyn Fn() + Send + 'static>) {
          std::thread::spawn(task);
      }
      ```
      
      ---
      
      ## 6. Interior Mutability
      
      ### Use Cell<T> for Copy Types
      
      `Cell<T>` allows mutation through a shared reference. It is `!Sync` (single-threaded only) and works only for `Copy` types.
      
      ```rust
      use std::cell::Cell;
      
      struct Counter {
          count: Cell<u32>,
      }
      
      impl Counter {
          fn increment(&self) { // &self, not &mut self
              self.count.set(self.count.get() + 1);
          }
          fn value(&self) -> u32 {
              self.count.get()
          }
      }
      
      let c = Counter { count: Cell::new(0) };
      c.increment();
      c.increment();
      println!("{}", c.value()); // 2
      ```
      
      ### Use RefCell<T> for Non-Copy Types
      
      `RefCell<T>` enforces borrow rules at runtime. Panics if rules are violated. Also `!Sync`.
      
      ```rust
      use std::cell::RefCell;
      
      let data = RefCell::new(vec![1, 2, 3]);
      
      // Immutable borrow
      let r = data.borrow();
      println!("{:?}", *r);
      drop(r); // release before mutable borrow
      
      // Mutable borrow
      data.borrow_mut().push(4);
      
      // try_borrow / try_borrow_mut avoid panics
      match data.try_borrow_mut() {
          Ok(mut v) => v.push(5),
          Err(_) => eprintln!("already borrowed"),
      }
      ```
      
      Common pattern: `Rc<RefCell<T>>` for shared, mutable ownership in single-threaded code.
      
      ```rust
      use std::rc::Rc;
      use std::cell::RefCell;
      
      let shared = Rc::new(RefCell::new(vec![]));
      let clone = Rc::clone(&shared);
      
      shared.borrow_mut().push(1);
      clone.borrow_mut().push(2);
      println!("{:?}", shared.borrow()); // [1, 2]
      ```
      
      ### Use OnceCell and OnceLock for Lazy Initialization
      
      `OnceCell<T>` initializes a value at most once. `OnceLock<T>` is the thread-safe version.
      
      ```rust
      use std::cell::OnceCell;
      
      struct Config {
          expensive: OnceCell<Vec<u8>>,
      }
      
      impl Config {
          fn data(&self) -> &Vec<u8> {
              self.expensive.get_or_init(|| {
                  expensive_computation()
              })
          }
      }
      
      // OnceLock for global statics (thread-safe)
      use std::sync::OnceLock;
      
      static INSTANCE: OnceLock<String> = OnceLock::new();
      
      fn get_instance() -> &'static String {
          INSTANCE.get_or_init(|| String::from("initialized once"))
      }
      ```
      
      ### Choose the Right Type
      
      | Type | Thread-safe | Works with | Runtime check |
      |------|-------------|------------|---------------|
      | `Cell<T>` | No | `Copy` types | No (get/set) |
      | `RefCell<T>` | No | Any `T` | Yes (panics) |
      | `OnceCell<T>` | No | Any `T` | No (init once) |
      | `OnceLock<T>` | Yes | Any `T: Send + Sync` | No (init once) |
      | `Mutex<T>` | Yes | Any `T: Send` | Blocks |
      | `RwLock<T>` | Yes | Any `T: Send + Sync` | Blocks |
      
      ---
      
      ## 7. Common Borrow Checker Patterns
      
      ### Split Borrows to Borrow Multiple Fields
      
      The borrow checker tracks fields independently. Access them through separate references.
      
      ```rust
      struct Point { x: f64, y: f64 }
      
      let mut p = Point { x: 1.0, y: 2.0 };
      
      // ERROR: cannot borrow p.x as mutable because p is also borrowed
      // let rx = &mut p.x;
      // let ry = &mut p.y;
      
      // OK: split into two mutable references to distinct fields
      let rx = &mut p.x;
      let ry = &mut p.y;
      *rx += 1.0;
      *ry += 1.0;
      ```
      
      For slices, use `split_at_mut`:
      
      ```rust
      let mut data = vec![1, 2, 3, 4, 5];
      let (left, right) = data.split_at_mut(2);
      left[0] = 10;
      right[0] = 30;
      ```
      
      ### Use Indices Instead of References
      
      When a data structure is being modified, holding an index avoids borrow conflicts.
      
      ```rust
      // BAD: first_ref holds a borrow while we try to modify vec
      // let first_ref = &vec[0];
      // vec.push(99); // ERROR
      
      // GOOD: store index, re-access after modification
      let first_idx = 0;
      vec.push(99);
      println!("{}", vec[first_idx]); // re-borrow, no conflict
      ```
      
      ### Use Temporary Variables to Shorten Borrow Scope
      
      Extracting a value before using it can satisfy the borrow checker.
      
      ```rust
      fn process(map: &mut HashMap<String, Vec<i32>>, key: &str) {
          // This fails: cannot borrow map as mutable while key is borrowed from it
          // if map.contains_key(key) {
          //     map.get_mut(key).unwrap().push(1);
          // }
      
          // Clone the key to avoid holding a reference into map
          let key = key.to_string();
          map.entry(key).or_default().push(1);
      }
      ```
      
      ### Use the Entry API for Maps
      
      `entry` combines lookup and insert in one operation, avoiding double borrows.
      
      ```rust
      use std::collections::HashMap;
      
      let mut scores: HashMap<String, Vec<i32>> = HashMap::new();
      
      // BAD: two separate borrows
      // if !scores.contains_key("Alice") {
      //     scores.insert("Alice".to_string(), vec![]);
      // }
      // scores.get_mut("Alice").unwrap().push(10);
      
      // GOOD: entry API
      scores.entry("Alice".to_string()).or_default().push(10);
      scores.entry("Bob".to_string()).or_insert_with(Vec::new).push(5);
      
      // Modify existing or insert computed value
      scores.entry("Carol".to_string())
          .and_modify(|v| v.push(99))
          .or_insert_with(|| vec![0]);
      ```
      
      ### Restructure Loops That Hold Borrows
      
      Collecting indices or keys before iterating avoids holding a reference during modification.
      
      ```rust
      let mut map: HashMap<i32, i32> = HashMap::new();
      map.insert(1, 10);
      map.insert(2, 20);
      
      // Collect keys first, then iterate
      let keys: Vec<i32> = map.keys().cloned().collect();
      for key in keys {
          if key % 2 == 0 {
              map.remove(&key); // OK: no active borrow from .keys()
          }
      }
      ```
      
      ---
      
      ## 8. NLL (Non-Lexical Lifetimes)
      
      ### Understand What NLL Provides
      
      Before NLL (pre-2018 edition), borrows lasted until the end of the lexical block. NLL ends borrows at the last point of use.
      
      ```rust
      let mut s = String::from("hello");
      
      let r = &s;
      println!("{}", r); // last use of r
      
      // Pre-NLL: ERROR here because r's scope extended to end of block
      // NLL: OK because r is no longer used after the println
      s.push_str(" world");
      println!("{}", s);
      ```
      
      ### Know NLL's Limits
      
      NLL does not help when a borrow is inside a loop or the returned reference ties back to self.
      
      ```rust
      // This still fails even with NLL - the borrow from get() ties to map
      fn first_or_insert(map: &mut HashMap<i32, i32>, key: i32) -> &i32 {
          if let Some(val) = map.get(&key) {
              return val; // borrows map
          }
          map.insert(key, 0); // ERROR: map already borrowed by return path
          map.get(&key).unwrap()
      }
      
      // Fix: use entry API
      fn first_or_insert_fixed(map: &mut HashMap<i32, i32>, key: i32) -> &i32 {
          map.entry(key).or_insert(0)
      }
      ```
      
      ---
      
      ## 9. Self-Referential Structs
      
      ### Understand Why Self-Referential Structs Fail
      
      A struct cannot hold a reference to one of its own fields because moving the struct would invalidate the reference.
      
      ```rust
      // This does NOT compile
      struct SelfRef {
          data: String,
          ptr: &str, // would need lifetime tied to self.data — impossible
      }
      ```
      
      ### Use the ouroboros Crate
      
      `ouroboros` generates safe self-referential structs via macro.
      
      ```rust
      // Cargo.toml: ouroboros = "0.18"
      use ouroboros::self_referencing;
      
      #[self_referencing]
      struct ParsedDocument {
          raw: String,
          #[borrows(raw)]
          #[covariant]
          parsed: Vec<&'this str>,
      }
      
      let doc = ParsedDocumentBuilder {
          raw: String::from("hello world foo"),
          parsed_builder: |raw: &str| raw.split_whitespace().collect(),
      }.build();
      
      doc.with_parsed(|words| println!("{:?}", words));
      ```
      
      ### Use Pin for Futures and Async
      
      `Pin<P>` prevents moving the pinned value. The async runtime uses it to allow self-referential futures.
      
      ```rust
      use std::pin::Pin;
      use std::marker::PhantomPinned;
      
      struct Unmovable {
          data: String,
          // self_ref would point into data
          _pin: PhantomPinned,
      }
      
      // Create pinned on heap
      let pinned = Box::pin(Unmovable {
          data: String::from("hello"),
          _pin: PhantomPinned,
      });
      
      // Can call methods through Pin
      // Cannot move out of Pin<Box<T>> if T: !Unpin
      ```
      
      In practice, `Pin` appears most often in custom `Future` implementations and when building async combinators. Prefer `async fn` and existing executor abstractions over manual `Pin` management.
      
    • testing.md 18.9 KB
      # Rust Testing Reference
      
      ## Table of Contents
      
      1. [Unit Tests](#1-unit-tests)
      2. [Integration Tests](#2-integration-tests)
      3. [Doc Tests](#3-doc-tests)
      4. [Async Tests](#4-async-tests)
      5. [mockall](#5-mockall)
      6. [Test Fixtures](#6-test-fixtures)
      7. [Property-Based Testing](#7-property-based-testing)
      8. [Benchmarks](#8-benchmarks)
      9. [Snapshot Testing](#9-snapshot-testing)
      10. [Test Organization](#10-test-organization)
      11. [CI Patterns](#11-ci-patterns)
      
      ---
      
      ## 1. Unit Tests
      
      ### Write Tests in `#[cfg(test)]` Modules
      
      ```rust
      pub fn divide(a: f64, b: f64) -> Result<f64, String> {
          if b == 0.0 {
              Err("division by zero".to_string())
          } else {
              Ok(a / b)
          }
      }
      
      #[cfg(test)]
      mod tests {
          use super::*;  // bring parent module into scope
      
          #[test]
          fn divide_positive_numbers() {
              assert_eq!(divide(10.0, 2.0), Ok(5.0));
          }
      
          #[test]
          fn divide_returns_error_on_zero() {
              assert!(divide(1.0, 0.0).is_err());
          }
      
          #[test]
          #[should_panic(expected = "index out of bounds")]
          fn panics_on_bad_index() {
              let v: Vec<i32> = vec![];
              let _ = v[0];
          }
      
          // Return Result from a test - failure message from the Err variant
          #[test]
          fn parse_valid_input() -> Result<(), String> {
              let n: i32 = "42".parse().map_err(|e: std::num::ParseIntError| e.to_string())?;
              assert_eq!(n, 42);
              Ok(())
          }
      }
      ```
      
      ### Use Assert Macros Effectively
      
      ```rust
      #[cfg(test)]
      mod tests {
          #[test]
          fn assert_variants() {
              let x = 5;
      
              assert!(x > 0);                          // boolean
              assert_eq!(x, 5);                        // equality (implements PartialEq + Debug)
              assert_ne!(x, 99);                       // inequality
              assert_eq!(x, 5, "Expected 5, got {}", x);  // with message
      
              // Floating point - check within epsilon
              let f = 0.1 + 0.2;
              assert!((f - 0.3).abs() < 1e-10, "float comparison failed: {}", f);
          }
      }
      ```
      
      ---
      
      ## 2. Integration Tests
      
      ### Organize Tests in the `tests/` Directory
      
      ```
      my_crate/
      ├── src/
      │   └── lib.rs
      └── tests/
          ├── common/
          │   └── mod.rs        # shared helpers (not a test file)
          ├── api_test.rs
          └── db_test.rs
      ```
      
      ```rust
      // tests/common/mod.rs - shared setup, not discovered as a test binary
      pub fn setup_logging() {
          let _ = tracing_subscriber::fmt::try_init();
      }
      
      pub fn load_fixture(name: &str) -> serde_json::Value {
          let path = std::path::Path::new("tests/fixtures").join(name);
          let bytes = std::fs::read(path).expect("fixture not found");
          serde_json::from_slice(&bytes).expect("invalid fixture JSON")
      }
      ```
      
      ```rust
      // tests/api_test.rs - each file becomes a separate test binary
      mod common;
      
      use my_crate::ApiClient;
      
      #[test]
      fn client_builds_with_defaults() {
          common::setup_logging();
          let client = ApiClient::new("http://localhost");
          assert_eq!(client.base_url(), "http://localhost");
      }
      ```
      
      ### Share State Between Integration Test Files
      
      ```rust
      // tests/common/mod.rs
      use std::sync::OnceLock;
      
      static SERVER: OnceLock<TestServer> = OnceLock::new();
      
      pub fn get_server() -> &'static TestServer {
          SERVER.get_or_init(|| TestServer::start())
      }
      ```
      
      ---
      
      ## 3. Doc Tests
      
      ### Write Testable Examples in Documentation
      
      ```rust
      /// Parses a version string into major, minor, patch components.
      ///
      /// # Examples
      ///
      /// ```
      /// use my_crate::parse_version;
      ///
      /// let (major, minor, patch) = parse_version("1.2.3").unwrap();
      /// assert_eq!((major, minor, patch), (1, 2, 3));
      /// ```
      ///
      /// Returns `None` for invalid input:
      ///
      /// ```
      /// use my_crate::parse_version;
      /// assert!(parse_version("not_a_version").is_none());
      /// ```
      pub fn parse_version(s: &str) -> Option<(u32, u32, u32)> {
          // ...
      }
      ```
      
      ### Use Hidden Setup Lines
      
      ```rust
      /// Demonstrates the cache in action.
      ///
      /// ```
      /// # use my_crate::Cache;
      /// # let mut cache = Cache::new(100);  // hidden: sets up state
      /// cache.insert("key", "value");
      /// assert_eq!(cache.get("key"), Some("value"));
      /// ```
      ```
      
      ### Mark Non-Runnable Examples
      
      ```rust
      /// Connect to the database.
      ///
      /// ```no_run
      /// # use my_crate::connect;
      /// // This compiles but does not run (needs a real database)
      /// let pool = connect("postgres://localhost/mydb").unwrap();
      /// ```
      ///
      /// This example is only shown, not compiled:
      ///
      /// ```ignore
      /// // Complex setup omitted
      /// some_impossible_setup();
      /// ```
      ///
      /// This example should fail to compile:
      ///
      /// ```compile_fail
      /// let x: u32 = "not a number";  // type error
      /// ```
      ```
      
      ---
      
      ## 4. Async Tests
      
      ### Test with `#[tokio::test]`
      
      ```rust
      #[tokio::test]
      async fn fetch_returns_data() {
          let client = build_client();
          let result = client.fetch("https://example.com").await;
          assert!(result.is_ok());
      }
      
      // Multi-thread runtime (matches production tokio::main)
      #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
      async fn concurrent_requests() {
          let (r1, r2) = tokio::join!(
              do_request("a"),
              do_request("b"),
          );
          assert!(r1.is_ok());
          assert!(r2.is_ok());
      }
      
      // Current-thread runtime (deterministic, good for unit tests)
      #[tokio::test(flavor = "current_thread")]
      async fn sequential_processing() {
          let result = process_sequentially(vec![1, 2, 3]).await;
          assert_eq!(result, vec![2, 4, 6]);
      }
      ```
      
      ### Mock Time with `tokio::time::pause`
      
      ```rust
      use tokio::time::{self, Duration, Instant};
      
      #[tokio::test]
      async fn cache_expires_after_ttl() {
          time::pause();  // freeze the clock
      
          let cache = Cache::with_ttl(Duration::from_secs(60));
          cache.insert("key", "value");
      
          assert_eq!(cache.get("key"), Some("value"));
      
          time::advance(Duration::from_secs(61)).await;  // advance clock
      
          assert_eq!(cache.get("key"), None);  // now expired
      }
      ```
      
      ---
      
      ## 5. mockall
      
      ```toml
      mockall = "0.12"
      ```
      
      ### Automock a Trait
      
      ```rust
      use mockall::automock;
      
      #[automock]
      pub trait UserRepository: Send + Sync {
          async fn find_by_id(&self, id: u64) -> Option<User>;
          async fn save(&self, user: &User) -> Result<(), DbError>;
          fn count(&self) -> usize;
      }
      ```
      
      ### Configure Expectations in Tests
      
      ```rust
      #[cfg(test)]
      mod tests {
          use super::*;
          use mockall::predicate::*;
      
          #[tokio::test]
          async fn get_user_returns_user_when_found() {
              let mut mock = MockUserRepository::new();
      
              mock.expect_find_by_id()
                  .with(eq(42u64))                    // match specific argument
                  .times(1)                           // must be called exactly once
                  .returning(|_| Some(User { id: 42, name: "Alice".to_string() }));
      
              let service = UserService::new(mock);
              let user = service.get_user(42).await.unwrap();
              assert_eq!(user.name, "Alice");
          }
      
          #[tokio::test]
          async fn get_user_returns_error_when_not_found() {
              let mut mock = MockUserRepository::new();
      
              mock.expect_find_by_id()
                  .returning(|_| None);  // any argument, always None
      
              let service = UserService::new(mock);
              let result = service.get_user(99).await;
              assert!(matches!(result, Err(ServiceError::NotFound)));
          }
      
          #[test]
          fn saves_only_valid_users() {
              let mut mock = MockUserRepository::new();
      
              mock.expect_save()
                  .withf(|user| !user.name.is_empty())  // custom predicate
                  .times(1)
                  .returning(|_| Ok(()));
      
              // mock verifies expectations on drop
          }
      }
      ```
      
      ### Chain Sequences of Calls
      
      ```rust
      use mockall::Sequence;
      
      #[test]
      fn retries_on_first_failure() {
          let mut mock = MockUserRepository::new();
          let mut seq = Sequence::new();
      
          mock.expect_count()
              .times(1)
              .in_sequence(&mut seq)
              .returning(|| 0);
      
          mock.expect_count()
              .times(1)
              .in_sequence(&mut seq)
              .returning(|| 5);
      
          assert_eq!(mock.count(), 0);
          assert_eq!(mock.count(), 5);
      }
      ```
      
      ### Mock Structs (not just traits)
      
      ```rust
      use mockall::mock;
      
      mock! {
          pub HttpClient {
              pub fn get(&self, url: &str) -> Result<String, reqwest::Error>;
              pub fn post(&self, url: &str, body: &str) -> Result<String, reqwest::Error>;
          }
      }
      ```
      
      ---
      
      ## 6. Test Fixtures
      
      ### Set Up and Tear Down with Drop
      
      ```rust
      pub struct TestDb {
          pub pool: sqlx::PgPool,
          pub db_name: String,
      }
      
      impl TestDb {
          pub async fn new() -> Self {
              let db_name = format!("test_{}", uuid::Uuid::new_v4().simple());
              let admin_pool = sqlx::PgPool::connect("postgres://localhost/postgres").await.unwrap();
      
              sqlx::query(&format!("CREATE DATABASE {}", db_name))
                  .execute(&admin_pool)
                  .await
                  .unwrap();
      
              let pool = sqlx::PgPool::connect(&format!("postgres://localhost/{}", db_name))
                  .await
                  .unwrap();
      
              sqlx::migrate!("./migrations").run(&pool).await.unwrap();
      
              TestDb { pool, db_name }
          }
      }
      
      impl Drop for TestDb {
          fn drop(&mut self) {
              // Schedule async cleanup - use a blocking approach here
              let db_name = self.db_name.clone();
              std::thread::spawn(move || {
                  let rt = tokio::runtime::Runtime::new().unwrap();
                  rt.block_on(async {
                      let pool = sqlx::PgPool::connect("postgres://localhost/postgres").await.unwrap();
                      sqlx::query(&format!("DROP DATABASE IF EXISTS {}", db_name))
                          .execute(&pool)
                          .await
                          .ok();
                  });
              });
          }
      }
      ```
      
      ### Share Expensive Setup with `OnceLock`
      
      ```rust
      use std::sync::OnceLock;
      
      static CONFIG: OnceLock<TestConfig> = OnceLock::new();
      
      fn test_config() -> &'static TestConfig {
          CONFIG.get_or_init(|| TestConfig::load_from_env())
      }
      
      #[test]
      fn uses_shared_config() {
          let config = test_config();
          assert!(!config.api_key.is_empty());
      }
      ```
      
      ### Use Temporary Directories
      
      ```rust
      use tempfile::TempDir;
      
      #[test]
      fn writes_output_file() {
          let dir = TempDir::new().unwrap();  // deleted on drop
          let file_path = dir.path().join("output.txt");
      
          write_results(&file_path, &[1, 2, 3]).unwrap();
      
          let contents = std::fs::read_to_string(&file_path).unwrap();
          assert!(contents.contains("1"));
      }
      
      // Keep dir alive for the test scope
      #[test]
      fn reads_fixture_from_temp() {
          let dir = TempDir::new().unwrap();
          std::fs::write(dir.path().join("input.json"), br#"{"key":"value"}"#).unwrap();
      
          let result = process_file(dir.path().join("input.json")).unwrap();
          assert_eq!(result.get("key").unwrap(), "value");
          // dir dropped here, cleanup happens
      }
      ```
      
      ---
      
      ## 7. Property-Based Testing
      
      ```toml
      proptest = "1"
      ```
      
      ### Write Property Tests
      
      ```rust
      use proptest::prelude::*;
      
      proptest! {
          #[test]
          fn parse_then_serialize_roundtrips(s in "[a-zA-Z0-9]{1,20}") {
              let parsed = parse_identifier(&s).unwrap();
              let serialized = serialize_identifier(&parsed);
              prop_assert_eq!(s, serialized);
          }
      
          #[test]
          fn sort_is_idempotent(mut v in prop::collection::vec(any::<i32>(), 0..100)) {
              v.sort();
              let sorted_once = v.clone();
              v.sort();
              prop_assert_eq!(sorted_once, v);
          }
      
          #[test]
          fn addition_commutes(a in 0i32..1000, b in 0i32..1000) {
              prop_assert_eq!(a + b, b + a);
          }
      }
      ```
      
      ### Derive `Arbitrary` for Custom Types
      
      ```rust
      use proptest_derive::Arbitrary;
      
      #[derive(Debug, Clone, Arbitrary)]
      pub struct User {
          #[proptest(regex = "[a-z]{3,20}")]
          pub username: String,
          pub age: u8,
          pub active: bool,
      }
      
      proptest! {
          #[test]
          fn user_validation_never_panics(user in any::<User>()) {
              // Should return Ok or Err, never panic
              let _ = validate_user(&user);
          }
      }
      ```
      
      ### Handle Shrinking and Regression Files
      
      Proptest automatically saves failing inputs to `proptest-regressions/` and replays them on subsequent runs. Commit these files to catch regressions. Suppress with `#[proptest(skip_shrink)]` for expensive types.
      
      ---
      
      ## 8. Benchmarks
      
      ```toml
      [dev-dependencies]
      criterion = { version = "0.5", features = ["html_reports"] }
      
      [[bench]]
      name = "my_bench"
      harness = false
      ```
      
      ### Write Criterion Benchmarks
      
      ```rust
      // benches/my_bench.rs
      use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
      use my_crate::{parse, process};
      
      fn bench_parse(c: &mut Criterion) {
          let input = "example input string";
      
          c.bench_function("parse_simple", |b| {
              b.iter(|| parse(criterion::black_box(input)))
          });
      }
      
      fn bench_process_sizes(c: &mut Criterion) {
          let mut group = c.benchmark_group("process");
      
          for size in [100usize, 1_000, 10_000] {
              let data: Vec<u8> = (0..size).map(|i| i as u8).collect();
      
              group.throughput(Throughput::Bytes(size as u64));
              group.bench_with_input(BenchmarkId::from_parameter(size), &data, |b, data| {
                  b.iter(|| process(criterion::black_box(data)))
              });
          }
      
          group.finish();
      }
      
      fn bench_comparison(c: &mut Criterion) {
          let mut group = c.benchmark_group("sort_comparison");
          let data: Vec<i32> = (0..1000).rev().collect();
      
          group.bench_function("std_sort", |b| {
              b.iter(|| {
                  let mut v = data.clone();
                  v.sort();
                  v
              })
          });
      
          group.bench_function("unstable_sort", |b| {
              b.iter(|| {
                  let mut v = data.clone();
                  v.sort_unstable();
                  v
              })
          });
      
          group.finish();
      }
      
      criterion_group!(benches, bench_parse, bench_process_sizes, bench_comparison);
      criterion_main!(benches);
      ```
      
      ### Run Benchmarks and Generate Flamegraphs
      
      ```bash
      # Run all benchmarks
      cargo bench
      
      # Run specific benchmark
      cargo bench --bench my_bench parse
      
      # Save baseline for comparison
      cargo bench -- --save-baseline before
      # ... make changes ...
      cargo bench -- --baseline before
      
      # Generate flamegraph (requires cargo-flamegraph and perf/dtrace)
      cargo flamegraph --bench my_bench -- --bench bench_parse
      ```
      
      ---
      
      ## 9. Snapshot Testing
      
      ```toml
      insta = { version = "1", features = ["json", "yaml", "redactions"] }
      ```
      
      ### Assert with Snapshots
      
      ```rust
      use insta::assert_snapshot;
      
      #[test]
      fn renders_report() {
          let report = generate_report(&sample_data());
          assert_snapshot!(report);
          // First run: creates snapshot file in snapshots/ directory
          // Subsequent runs: compares against saved snapshot
      }
      
      // JSON snapshots (pretty-printed, sorted keys)
      use insta::assert_json_snapshot;
      
      #[test]
      fn serializes_user() {
          let user = User { id: 1, name: "Alice".into(), active: true };
          assert_json_snapshot!(user);
      }
      ```
      
      ### Use Redactions for Dynamic Values
      
      ```rust
      use insta::assert_json_snapshot;
      
      #[test]
      fn snapshot_with_dynamic_id() {
          let response = create_item("test");
          assert_json_snapshot!(response, {
              ".id" => "[id]",                // replace dynamic id
              ".created_at" => "[timestamp]", // replace timestamp
          });
      }
      ```
      
      ### Review and Accept Snapshots
      
      ```bash
      # Install the review tool
      cargo install cargo-insta
      
      # Run tests (failures create .snap.new files)
      cargo test
      
      # Review all pending snapshots interactively
      cargo insta review
      
      # Accept all pending snapshots at once
      cargo insta accept
      ```
      
      Commit `.snap` files alongside code. They are the expected output and act as documentation.
      
      ---
      
      ## 10. Test Organization
      
      ### Build a Common Test Utilities Module
      
      ```
      tests/
      ├── common/
      │   ├── mod.rs          # re-exports all helpers
      │   ├── fixtures.rs     # load JSON/TOML test data
      │   ├── builders.rs     # test builder patterns for structs
      │   └── assertions.rs   # custom assert helpers
      ```
      
      ```rust
      // tests/common/builders.rs
      pub struct UserBuilder {
          id: u64,
          name: String,
          email: String,
      }
      
      impl UserBuilder {
          pub fn new() -> Self {
              UserBuilder { id: 1, name: "Test User".into(), email: "test@example.com".into() }
          }
          pub fn id(mut self, id: u64) -> Self { self.id = id; self }
          pub fn name(mut self, name: impl Into<String>) -> Self { self.name = name.into(); self }
          pub fn build(self) -> User {
              User { id: self.id, name: self.name, email: self.email }
          }
      }
      ```
      
      ### Extract a `test-utils` Workspace Crate
      
      For large workspaces, extract test utilities into a dedicated crate:
      
      ```toml
      # Cargo.toml (workspace root)
      [workspace]
      members = ["my-app", "my-lib", "test-utils"]
      
      # my-lib/Cargo.toml
      [dev-dependencies]
      test-utils = { path = "../test-utils" }
      ```
      
      This avoids duplicating helpers across crates and allows `#[cfg(test)]`-gated re-exports.
      
      ### Write Custom Assertion Helpers
      
      ```rust
      // tests/common/assertions.rs
      pub fn assert_sorted<T: Ord + std::fmt::Debug>(items: &[T]) {
          for window in items.windows(2) {
              assert!(
                  window[0] <= window[1],
                  "Expected sorted slice, found {:?} before {:?}",
                  window[0], window[1]
              );
          }
      }
      
      pub fn assert_error_contains(result: &anyhow::Result<()>, expected: &str) {
          match result {
              Err(e) => assert!(
                  e.to_string().contains(expected),
                  "Expected error to contain '{}', got: {}",
                  expected, e
              ),
              Ok(_) => panic!("Expected error containing '{}', got Ok", expected),
          }
      }
      ```
      
      ---
      
      ## 11. CI Patterns
      
      ### Run the Full Test Suite
      
      ```yaml
      # .github/workflows/ci.yml
      name: CI
      
      on: [push, pull_request]
      
      jobs:
        test:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v4
            - uses: dtolnay/rust-toolchain@stable
              with:
                components: clippy, rustfmt
            - uses: Swatinem/rust-cache@v2
      
            - name: Format check
              run: cargo fmt --all -- --check
      
            - name: Lint
              run: cargo clippy --all-targets --all-features -- -D warnings
      
            - name: Test
              run: cargo test --workspace --all-features
              env:
                DATABASE_URL: postgres://postgres:postgres@localhost/test
      
            - name: Doc test
              run: cargo test --doc --workspace
      ```
      
      ### Test a Feature Matrix
      
      ```yaml
      strategy:
        matrix:
          features: ["", "feature-a", "feature-b", "full"]
      steps:
        - name: Test feature set
          run: cargo test --no-default-features --features "${{ matrix.features }}"
      ```
      
      ### Measure Coverage with `cargo-llvm-cov`
      
      ```bash
      # Install
      cargo install cargo-llvm-cov
      
      # Generate coverage report
      cargo llvm-cov --workspace --all-features --lcov --output-path lcov.info
      
      # HTML report locally
      cargo llvm-cov --workspace --html
      open target/llvm-cov/html/index.html
      ```
      
      ```yaml
      # In CI
      - name: Coverage
        run: cargo llvm-cov --workspace --all-features --lcov --output-path lcov.info
      - uses: codecov/codecov-action@v4
        with:
          files: lcov.info
      ```
      
      ### Run Tests Against a Live Database in CI
      
      ```yaml
      services:
        postgres:
          image: postgres:16
          env:
            POSTGRES_PASSWORD: postgres
            POSTGRES_DB: test
          ports:
            - 5432:5432
          options: >-
            --health-cmd pg_isready
            --health-interval 10s
            --health-timeout 5s
            --health-retries 5
      ```
      
      ### Check for Unused Dependencies
      
      ```bash
      cargo install cargo-machete
      cargo machete
      
      # Or for dependency audit
      cargo install cargo-audit
      cargo audit
      ```
      
      ### Enforce MSRV (Minimum Supported Rust Version)
      
      ```toml
      # Cargo.toml
      [package]
      rust-version = "1.75"
      ```
      
      ```yaml
      - uses: dtolnay/rust-toolchain@1.75
      - run: cargo test --workspace
      ```
      
    • traits-generics.md 16.5 KB
      # Traits and Generics Reference
      
      ## Table of Contents
      
      1. [Trait Definition](#1-trait-definition)
      2. [Trait Bounds](#2-trait-bounds)
      3. [Associated Types vs Generic Parameters](#3-associated-types-vs-generic-parameters)
      4. [Supertraits](#4-supertraits)
      5. [Trait Objects](#5-trait-objects)
      6. [Derive Macros](#6-derive-macros)
      7. [Common Trait Implementations](#7-common-trait-implementations)
      8. [Sealed Traits](#8-sealed-traits)
      9. [Extension Traits](#9-extension-traits)
      10. [Generics](#10-generics)
      11. [Blanket Implementations](#11-blanket-implementations)
      
      ---
      
      ## 1. Trait Definition
      
      ### Define Methods, Default Implementations, and Associated Functions
      
      ```rust
      pub trait Greet {
          // Required method — implementors must provide this
          fn name(&self) -> &str;
      
          // Default method — implementors may override
          fn greeting(&self) -> String {
              format!("Hello, {}!", self.name())
          }
      
          // Associated function (no self) — often used as constructors
          fn kind() -> &'static str {
              "greeter"
          }
      }
      
      struct Person {
          name: String,
      }
      
      impl Greet for Person {
          fn name(&self) -> &str {
              &self.name
          }
          // greeting() uses the default implementation
      }
      
      let p = Person { name: "Alice".into() };
      println!("{}", p.greeting()); // "Hello, Alice!"
      println!("{}", Person::kind()); // "greeter"
      ```
      
      ### Define Traits with Associated Types and Constants
      
      ```rust
      pub trait Encode {
          type Output;
          const VERSION: u8 = 1;
      
          fn encode(&self) -> Self::Output;
      }
      
      struct Json(String);
      
      impl Encode for Json {
          type Output = Vec<u8>;
          const VERSION: u8 = 2; // override default
      
          fn encode(&self) -> Vec<u8> {
              self.0.as_bytes().to_vec()
          }
      }
      ```
      
      ---
      
      ## 2. Trait Bounds
      
      ### Apply Bounds to Functions
      
      ```rust
      // Inline bound
      fn print_item<T: std::fmt::Display>(item: T) {
          println!("{}", item);
      }
      
      // Where clause — cleaner for multiple or complex bounds
      fn log<T, E>(result: Result<T, E>)
      where
          T: std::fmt::Debug,
          E: std::fmt::Display,
      {
          match result {
              Ok(v) => println!("OK: {:?}", v),
              Err(e) => eprintln!("ERR: {}", e),
          }
      }
      
      // Multiple bounds with +
      fn serialize_and_print<T: serde::Serialize + std::fmt::Debug>(val: &T) {
          println!("{:?}", val);
          let json = serde_json::to_string(val).unwrap();
          println!("{}", json);
      }
      ```
      
      ### Use impl Trait in Argument Position
      
      `impl Trait` in argument position is syntactic sugar for a generic parameter with that bound. Each call site can use a different concrete type.
      
      ```rust
      // These are equivalent
      fn process(item: impl std::fmt::Display) { println!("{}", item); }
      fn process<T: std::fmt::Display>(item: T) { println!("{}", item); }
      
      // impl Trait in return position — hides the concrete type
      fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
          move |y| x + y
      }
      ```
      
      Note: `impl Trait` in return position always returns the same concrete type — it is not a trait object. Use `Box<dyn Trait>` when you need to return different types.
      
      ### Apply Bounds to Structs and Impls
      
      ```rust
      struct Wrapper<T: Clone> {
          val: T,
      }
      
      // Bound on impl — methods only available when T: Clone + std::fmt::Debug
      impl<T: Clone + std::fmt::Debug> Wrapper<T> {
          fn inspect(&self) -> T {
              println!("{:?}", self.val);
              self.val.clone()
          }
      }
      ```
      
      ---
      
      ## 3. Associated Types vs Generic Parameters
      
      ### Choose Associated Types for One-to-One Relationships
      
      Use associated types when there is only one sensible implementation per type. `Iterator` is the canonical example — a type can only produce one kind of item.
      
      ```rust
      // Associated type: Vec<i32> implements Iterator<Item = &i32>
      // There is exactly one Item type per implementor
      trait Iterator {
          type Item;
          fn next(&mut self) -> Option<Self::Item>;
      }
      
      // Caller syntax is clean
      fn sum_iter<I: Iterator<Item = i32>>(mut it: I) -> i32 {
          let mut total = 0;
          while let Some(n) = it.next() { total += n; }
          total
      }
      ```
      
      ### Choose Generic Parameters for Multiple Implementations
      
      Use generic parameters when a type may implement the trait for many different type arguments. `From<T>` is the canonical example — `String` implements `From<&str>`, `From<char>`, etc.
      
      ```rust
      // Generic parameter: String can implement Converter for many T
      trait Converter<T> {
          fn convert(&self) -> T;
      }
      
      struct Celsius(f64);
      
      impl Converter<f64> for Celsius {
          fn convert(&self) -> f64 { self.0 }
      }
      
      impl Converter<String> for Celsius {
          fn convert(&self) -> String { format!("{}°C", self.0) }
      }
      
      let c = Celsius(100.0);
      let f: f64 = c.convert();
      let s: String = c.convert();
      ```
      
      ---
      
      ## 4. Supertraits
      
      ### Require Other Traits
      
      A supertrait is a trait that must be implemented before another trait can be implemented. Declare it with `:` after the trait name.
      
      ```rust
      use std::fmt;
      
      // Animal requires Display and Debug
      trait Animal: fmt::Display + fmt::Debug {
          fn sound(&self) -> &str;
      }
      
      #[derive(Debug)]
      struct Dog;
      
      impl fmt::Display for Dog {
          fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
              write!(f, "Dog")
          }
      }
      
      impl Animal for Dog {
          fn sound(&self) -> &str { "woof" }
      }
      ```
      
      ### Coerce to a Supertrait Object
      
      You can use a trait object of the supertrait when you only need shared functionality.
      
      ```rust
      fn describe(animal: &dyn fmt::Display) {
          println!("{}", animal);
      }
      
      let dog = Dog;
      describe(&dog as &dyn fmt::Display);
      ```
      
      ---
      
      ## 5. Trait Objects
      
      ### Create and Use dyn Trait
      
      A trait object (`dyn Trait`) is a fat pointer: a data pointer plus a vtable pointer. They enable dynamic dispatch.
      
      ```rust
      trait Shape {
          fn area(&self) -> f64;
      }
      
      struct Circle { radius: f64 }
      struct Square { side: f64 }
      
      impl Shape for Circle {
          fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
      }
      
      impl Shape for Square {
          fn area(&self) -> f64 { self.side * self.side }
      }
      
      // Heterogeneous collection via Box<dyn Trait>
      let shapes: Vec<Box<dyn Shape>> = vec![
          Box::new(Circle { radius: 1.0 }),
          Box::new(Square { side: 2.0 }),
      ];
      
      for shape in &shapes {
          println!("area = {:.2}", shape.area());
      }
      ```
      
      ### Satisfy Object Safety Rules
      
      A trait is object-safe (usable as `dyn Trait`) if:
      - It has no methods that return `Self`
      - It has no generic methods
      - All methods are dispatchable (take `&self`, `&mut self`, or `Box<Self>`)
      
      ```rust
      // NOT object-safe: clone() returns Self
      // trait Cloneable: Clone {} // cannot be dyn
      
      // Object-safe version: return Box<dyn Trait>
      trait DynClone {
          fn clone_box(&self) -> Box<dyn DynClone>;
      }
      
      // NOT object-safe: generic method
      trait Bad {
          fn convert<T>(&self) -> T; // generic method — not dispatchable
      }
      
      // OK: use associated type instead
      trait Good {
          type Output;
          fn convert(&self) -> Self::Output;
      }
      ```
      
      ### Add Send + Sync to Trait Objects for Threads
      
      ```rust
      // Sendable trait object
      fn spawn_worker(task: Box<dyn Fn() + Send + 'static>) {
          std::thread::spawn(task);
      }
      
      // Arc<dyn Trait + Send + Sync> for shared access across threads
      use std::sync::Arc;
      let shared: Arc<dyn Shape + Send + Sync> = Arc::new(Circle { radius: 1.0 });
      ```
      
      ---
      
      ## 6. Derive Macros
      
      ### Use Common Derives
      
      ```rust
      #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
      struct Config {
          name: String,
          value: u32,
      }
      
      // Debug: {:?} and {:#?} formatting
      // Clone: .clone() method
      // PartialEq/Eq: == and != operators
      // Hash: usable as HashMap/HashSet key (requires PartialEq + Eq)
      // Default: Config::default() returns Config { name: "", value: 0 }
      
      #[derive(PartialOrd, Ord, PartialEq, Eq)]
      struct Version(u32, u32, u32);
      
      // PartialOrd/Ord: <, >, <=, >= operators; enables .sort() on Vec<Version>
      // Ord requires PartialOrd; PartialOrd requires PartialEq
      ```
      
      ### Extend with the derive_more Crate
      
      `derive_more` provides derives for common trait impls that the standard library does not include.
      
      ```rust
      // Cargo.toml: derive_more = { version = "1", features = ["display", "from", "into"] }
      use derive_more::{Display, From, Into};
      
      #[derive(Display, From, Into)]
      #[display("User({name}, {id})")]
      struct UserId {
          name: String,
          id: u64,
      }
      
      let id = UserId::from(("Alice".to_string(), 42u64));
      let (name, num): (String, u64) = id.into();
      ```
      
      ---
      
      ## 7. Common Trait Implementations
      
      ### Implement Display
      
      ```rust
      use std::fmt;
      
      struct Point { x: f64, y: f64 }
      
      impl fmt::Display for Point {
          fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
              write!(f, "({:.2}, {:.2})", self.x, self.y)
          }
      }
      
      // Implementing Display gives .to_string() for free via blanket impl
      let p = Point { x: 1.0, y: 2.0 };
      println!("{}", p);
      let s: String = p.to_string();
      ```
      
      ### Implement From and Into
      
      Implement `From`; `Into` is derived automatically via a blanket impl.
      
      ```rust
      struct Meters(f64);
      struct Feet(f64);
      
      impl From<Meters> for Feet {
          fn from(m: Meters) -> Self {
              Feet(m.0 * 3.28084)
          }
      }
      
      let m = Meters(1.0);
      let f: Feet = m.into();      // Into<Feet> for Meters — derived from From
      let f2 = Feet::from(Meters(2.0)); // From<Meters> for Feet
      ```
      
      ### Implement FromStr
      
      ```rust
      use std::str::FromStr;
      
      #[derive(Debug)]
      struct Color { r: u8, g: u8, b: u8 }
      
      #[derive(Debug)]
      struct ParseColorError(String);
      
      impl std::fmt::Display for ParseColorError {
          fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
              write!(f, "parse color error: {}", self.0)
          }
      }
      
      impl FromStr for Color {
          type Err = ParseColorError;
      
          fn from_str(s: &str) -> Result<Self, Self::Err> {
              let parts: Vec<&str> = s.split(',').collect();
              if parts.len() != 3 {
                  return Err(ParseColorError("expected R,G,B".into()));
              }
              let parse = |p: &str| p.trim().parse::<u8>()
                  .map_err(|e| ParseColorError(e.to_string()));
              Ok(Color { r: parse(parts[0])?, g: parse(parts[1])?, b: parse(parts[2])? })
          }
      }
      
      let c: Color = "255, 128, 0".parse().unwrap();
      ```
      
      ### Implement Deref and DerefMut
      
      `Deref` enables the `*` operator and auto-deref coercions. Use it for smart-pointer-like types, not general type conversions.
      
      ```rust
      use std::ops::{Deref, DerefMut};
      
      struct Wrapper<T>(Vec<T>);
      
      impl<T> Deref for Wrapper<T> {
          type Target = Vec<T>;
          fn deref(&self) -> &Vec<T> { &self.0 }
      }
      
      impl<T> DerefMut for Wrapper<T> {
          fn deref_mut(&mut self) -> &mut Vec<T> { &mut self.0 }
      }
      
      let mut w = Wrapper(vec![1, 2, 3]);
      w.push(4);         // DerefMut: Vec::push via auto-deref
      println!("{}", w.len()); // Deref: Vec::len via auto-deref
      ```
      
      ### Implement AsRef and AsMut
      
      `AsRef<T>` is for cheap reference conversions. Prefer it over `Deref` in function parameters.
      
      ```rust
      // Accept String, &str, PathBuf, &Path, etc. — anything that is AsRef<str>
      fn print_upper(s: impl AsRef<str>) {
          println!("{}", s.as_ref().to_uppercase());
      }
      
      print_upper("hello");
      print_upper(String::from("world"));
      
      // AsRef<Path> for filesystem functions
      fn read_config(path: impl AsRef<std::path::Path>) -> std::io::Result<String> {
          std::fs::read_to_string(path)
      }
      ```
      
      ---
      
      ## 8. Sealed Traits
      
      ### Prevent External Implementations
      
      The sealed trait pattern restricts who can implement a trait — useful for stable API surfaces in libraries.
      
      ```rust
      // In your library crate
      mod private {
          pub trait Sealed {}
      }
      
      pub trait MyTrait: private::Sealed {
          fn do_thing(&self);
      }
      
      // Implement Sealed only for types you control
      pub struct TypeA;
      pub struct TypeB;
      
      impl private::Sealed for TypeA {}
      impl private::Sealed for TypeB {}
      
      impl MyTrait for TypeA {
          fn do_thing(&self) { println!("A"); }
      }
      
      impl MyTrait for TypeB {
          fn do_thing(&self) { println!("B"); }
      }
      
      // External users CANNOT implement MyTrait because they cannot implement
      // private::Sealed (it's not publicly accessible)
      ```
      
      ---
      
      ## 9. Extension Traits
      
      ### Add Methods to Foreign Types
      
      Extension traits let you add methods to types you do not own, including primitives and standard library types.
      
      ```rust
      pub trait StrExt {
          fn word_count(&self) -> usize;
          fn capitalize(&self) -> String;
      }
      
      impl StrExt for str {
          fn word_count(&self) -> usize {
              self.split_whitespace().count()
          }
      
          fn capitalize(&self) -> String {
              let mut chars = self.chars();
              match chars.next() {
                  None => String::new(),
                  Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
              }
          }
      }
      
      // Bring the trait into scope to use the methods
      use crate::StrExt;
      println!("{}", "hello world".word_count()); // 2
      println!("{}", "hello".capitalize());       // "Hello"
      ```
      
      Extension traits must be in scope (imported) to use their methods. This is why `use std::io::Write` and similar imports are necessary.
      
      ---
      
      ## 10. Generics
      
      ### Use Type Parameters
      
      ```rust
      // Generic struct
      struct Stack<T> {
          items: Vec<T>,
      }
      
      impl<T> Stack<T> {
          fn push(&mut self, item: T) { self.items.push(item); }
          fn pop(&mut self) -> Option<T> { self.items.pop() }
          fn is_empty(&self) -> bool { self.items.is_empty() }
      }
      
      // Generic enum
      enum Either<L, R> {
          Left(L),
          Right(R),
      }
      ```
      
      ### Use Const Generics
      
      Const generics allow types to be parameterized by constant values (integers, booleans, chars).
      
      ```rust
      // Array type parameterized by size — zero-cost abstraction
      struct Matrix<T, const ROWS: usize, const COLS: usize> {
          data: [[T; COLS]; ROWS],
      }
      
      impl<T: Default + Copy, const R: usize, const C: usize> Matrix<T, R, C> {
          fn new() -> Self {
              Matrix { data: [[T::default(); C]; R] }
          }
      
          fn rows(&self) -> usize { R }
          fn cols(&self) -> usize { C }
      }
      
      let m: Matrix<f64, 3, 4> = Matrix::new();
      assert_eq!(m.rows(), 3);
      ```
      
      ### Use PhantomData for Marker Types
      
      `PhantomData<T>` tells the compiler that a type logically contains `T` without storing it, affecting variance and drop checking.
      
      ```rust
      use std::marker::PhantomData;
      
      // A typed ID that cannot be mixed between entity types
      struct Id<T> {
          value: u64,
          _phantom: PhantomData<T>,
      }
      
      impl<T> Id<T> {
          fn new(value: u64) -> Self {
              Id { value, _phantom: PhantomData }
          }
      }
      
      struct User;
      struct Order;
      
      let user_id: Id<User> = Id::new(1);
      let order_id: Id<Order> = Id::new(1);
      // Cannot mix: user_id and order_id are different types even though value is same
      ```
      
      ### Use the Turbofish Syntax
      
      When the compiler cannot infer a generic type argument, use `::<>` (turbofish) to supply it explicitly.
      
      ```rust
      // Collect requires knowing what to collect into
      let nums: Vec<i32> = "1 2 3".split(' ')
          .map(|s| s.parse::<i32>().unwrap()) // turbofish on parse
          .collect::<Vec<_>>();               // turbofish on collect (alternative to type annotation)
      
      // Any generic function may need turbofish
      fn identity<T>(val: T) -> T { val }
      let x = identity::<String>(String::from("hello"));
      ```
      
      ### Express Lifetime Constraints on Generic Types
      
      `T: 'a` means "all references inside T live at least as long as 'a". This is required when storing generic types behind references.
      
      ```rust
      struct Holder<'a, T: 'a> {
          reference: &'a T,
      }
      
      // 'static bound: T contains no non-static references
      // (common for thread-spawning and stored callbacks)
      fn store_callback<F: Fn() + Send + 'static>(f: F) {
          std::thread::spawn(f);
      }
      ```
      
      ---
      
      ## 11. Blanket Implementations
      
      ### Understand Blanket Impls
      
      A blanket implementation applies a trait to any type that satisfies certain bounds, rather than to a specific named type.
      
      ```rust
      // From the standard library — any T that implements Display also gets ToString
      impl<T: std::fmt::Display> ToString for T {
          fn to_string(&self) -> String {
              format!("{}", self)
          }
      }
      
      // This is why any Display type has .to_string() for free
      42i32.to_string();
      3.14f64.to_string();
      ```
      
      ### Write Blanket Impls for Your Own Traits
      
      ```rust
      trait Summary {
          fn summarize(&self) -> String;
      }
      
      // Any type that implements Display also gets a free Summary impl
      impl<T: std::fmt::Display> Summary for T {
          fn summarize(&self) -> String {
              format!("Summary: {}", self)
          }
      }
      ```
      
      Be careful: blanket impls can create conflicts. Two blanket impls that could overlap will fail to compile (the orphan rule plus coherence checking prevents ambiguity).
      
      ### Apply the Orphan Rule
      
      You can implement a trait for a type only if either the trait or the type is defined in your crate. Both cannot be foreign.
      
      ```rust
      // OK: MyTrait (yours) for String (foreign)
      impl MyTrait for String { ... }
      
      // OK: Display (foreign) for MyType (yours)
      impl std::fmt::Display for MyType { ... }
      
      // ERROR: Display (foreign) for Vec<T> (foreign) — orphan rule violation
      // impl std::fmt::Display for Vec<i32> { ... }
      ```
      
      Work around this using the newtype pattern:
      
      ```rust
      struct MyVec(Vec<i32>);
      
      impl std::fmt::Display for MyVec {
          fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
              write!(f, "{:?}", self.0)
          }
      }
      ```
      
  • scripts
    • check-rust-facts.py 11.3 KB
      #!/usr/bin/env python3
      """Staleness verifier for rust-ops: the version-bearing facts the skill
      encodes must stay real and cited.
      
      rust-ops anchors its currency to a few version-bearing facts — the major
      versions of its core ecosystem crates (tokio, axum, serde). That is exactly
      the fact that drifts silently (SKILL-RESOURCE-PROTOCOL.md §7): a crate leaves
      its documented major upstream (axum 0.x → 1.0, say), or the prose stops naming
      a crate the catalog still commits to, and nobody notices for months. Two
      modes guard it:
      
        --offline (default, safe for PR CI): structural consistency, no network.
          * assets/rust-facts.json parses; every entry has name + documented_major
          * every catalogued crate is still named somewhere in the skill prose
            (SKILL.md / references/*.md) — the catalog can't drift from the docs
          * SKILL.md still carries a dated "as of 20XX" currency note
        --live (scheduled freshness.yml, never a PR gate): does each crate's
          latest stable major on crates.io still match the documented major? A
          newer major = the skill is behind reality (drift). Exit 7 if crates.io
          is unreachable. (crates.io requires a User-Agent header or it 403s.)
      
      Usage:   check-rust-facts.py [--offline | --live] [--catalog FILE] [--skill DIR] [--json] [--timeout S]
      Input:   argv flags only (no stdin).
      Output:  stdout = findings (plain rows, or a --json envelope). Data only.
      Stderr:  the verdict line, notices, errors.
      Exit:    0 ok, 2 usage, 3 catalog/skill missing, 4 catalog unparseable,
               7 crates.io unreachable (live, advisory — never a real failure),
               10 drift found (offline: uncited/undocumented/no currency note;
                               live: published major newer than documented major)
      
      Examples:
        check-rust-facts.py --offline                 # PR CI: catalog ⇆ prose consistency
        check-rust-facts.py --live                    # weekly: every crate's major still matches crates.io
        check-rust-facts.py --offline --json | jq '.data[]'
      """
      from __future__ import annotations
      
      import argparse
      import json
      import os
      import re
      import sys
      import urllib.error
      import urllib.parse
      import urllib.request
      from pathlib import Path
      
      EX_OK = 0
      EX_USAGE = 2
      EX_NOTFOUND = 3
      EX_UNPARSEABLE = 4
      EX_UNAVAILABLE = 7
      EX_DRIFT = 10
      
      HERE = Path(__file__).resolve().parent
      DEFAULT_CATALOG = HERE.parent / "assets" / "rust-facts.json"
      DEFAULT_SKILL = HERE.parent
      DEFAULT_REGISTRY = "https://crates.io/api/v1/crates"
      SCHEMA = "claude-mods.rust-ops.facts/v1"
      CURRENCY_RE = re.compile(r"as of 20\d\d")
      
      
      def eprint(*a) -> None:
          print(*a, file=sys.stderr)
      
      
      class Term:
          """Minimal ANSI helper (term.sh is bash-only; per TERMINAL-DESIGN.md §9 the
          Python port is inline). Honors FORCE_COLOR / NO_COLOR / TERM_ASCII and the
          bound stream's TTY + encoding so piped data stays plain ASCII."""
      
          _C = {"green": "\033[32m", "red": "\033[31m", "dim": "\033[2m", "off": "\033[0m"}
      
          def __init__(self, stream=sys.stderr) -> None:
              enc = (getattr(stream, "encoding", "") or "").lower()
              self.ascii = os.environ.get("TERM_ASCII") == "1" or "utf" not in enc
              if os.environ.get("FORCE_COLOR"):
                  self.color = True
              elif (os.environ.get("NO_COLOR") is not None
                    or os.environ.get("TERM") == "dumb"
                    or not getattr(stream, "isatty", lambda: False)()):
                  self.color = False
              else:
                  self.color = True
      
          def c(self, name: str, text: str) -> str:
              return f"{self._C.get(name, '')}{text}{self._C['off']}" if self.color else text
      
          def mark(self, ok: bool) -> str:
              g = ("+" if self.ascii else "✓") if ok else ("x" if self.ascii else "✗")
              return self.c("green" if ok else "red", g)
      
      
      def load_catalog(path: Path) -> tuple[list[dict], str]:
          """Returns (packages, registry). Each package has name + documented_major."""
          if not path.is_file():
              eprint(f"error: package catalog not found: {path}")
              raise SystemExit(EX_NOTFOUND)
          try:
              data = json.loads(path.read_text(encoding="utf-8"))
              pkgs = data["packages"]
              if not isinstance(pkgs, list) or not pkgs:
                  raise ValueError("'packages' must be a non-empty array")
              for p in pkgs:
                  if not isinstance(p, dict) or "name" not in p or "documented_major" not in p:
                      raise ValueError(f"package entry missing name/documented_major: {p!r}")
                  dm = p["documented_major"]
                  if not isinstance(dm, int) or isinstance(dm, bool) or dm < 0:
                      raise ValueError(f"documented_major must be a non-negative int: {p!r}")
              registry = data.get("registry") or DEFAULT_REGISTRY
              return pkgs, registry
          except (json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
              eprint(f"error: could not parse catalog {path}: {exc}")
              raise SystemExit(EX_UNPARSEABLE)
      
      
      def read_corpus(skill_dir: Path) -> tuple[str, str]:
          """Returns (skill_md_text, all_prose_text) across SKILL.md + references/*.md."""
          doc = skill_dir / "SKILL.md"
          if not doc.is_file():
              eprint(f"error: SKILL.md not found under {skill_dir}")
              raise SystemExit(EX_NOTFOUND)
          skill_md = doc.read_text(encoding="utf-8")
          parts = [skill_md]
          for ref in sorted((skill_dir / "references").glob("*.md")):
              parts.append(ref.read_text(encoding="utf-8"))
          return skill_md, "\n".join(parts)
      
      
      def check_offline(pkgs: list[dict], skill_dir: Path) -> list[dict]:
          skill_md, corpus = read_corpus(skill_dir)
          findings: list[dict] = []
          for p in pkgs:
              name = p["name"]
              # case-sensitive exact substring: crate names are case-sensitive (serde != Serde)
              if name not in corpus:
                  findings.append({"package": name, "issue": "catalogued but not named in skill prose"})
          if not CURRENCY_RE.search(skill_md):
              findings.append({"package": "(SKILL.md)", "issue": "no dated 'as of 20XX' currency note"})
          return findings
      
      
      def crates_latest(registry: str, name: str, timeout: float) -> tuple[str, object]:
          """Return ('ok', version_str) | ('gone', None) | ('unreachable', info).
      
          crates.io 403s requests without a User-Agent header, so one is mandatory."""
          url = registry.rstrip("/") + "/" + urllib.parse.quote(name, safe="")
          req = urllib.request.Request(url, method="GET",
                                       headers={"User-Agent": "claude-mods-rust-ops-check/1",
                                                "Accept": "application/json"})
          try:
              with urllib.request.urlopen(req, timeout=timeout) as resp:
                  data = json.loads(resp.read().decode("utf-8"))
                  crate = data.get("crate", {}) if isinstance(data, dict) else {}
                  # max_stable_version skips pre-releases (0.8.0-beta.1); fall back to max_version.
                  ver = crate.get("max_stable_version") or crate.get("max_version", "")
                  return ("ok", ver)
          except urllib.error.HTTPError as exc:
              if exc.code in (404, 410):
                  return ("gone", None)
              return ("unreachable", exc.code)  # 403/5xx etc: transient or auth, not a content finding
          except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
              return ("unreachable", str(getattr(exc, "reason", exc)))
      
      
      def major_of(version: str) -> int | None:
          m = re.match(r"\D*(\d+)", version or "")
          return int(m.group(1)) if m else None
      
      
      def check_live(pkgs: list[dict], registry: str, timeout: float) -> tuple[list[dict], list[dict]]:
          drift: list[dict] = []
          unreachable: list[dict] = []
          for p in pkgs:
              name = p["name"]
              doc = p["documented_major"]
              status, info = crates_latest(registry, name, timeout)
              if status == "gone":
                  drift.append({"package": name, "issue": "no longer resolves on crates.io (404)"})
              elif status != "ok":
                  unreachable.append({"package": name, "issue": f"unreachable: {info}"})
              else:
                  live_major = major_of(str(info))
                  if live_major is None:
                      unreachable.append({"package": name, "issue": f"could not parse version {info!r}"})
                  elif live_major > doc:
                      drift.append({"package": name,
                                    "issue": f"crates.io@{info} major {live_major} > documented major {doc}"})
          return drift, unreachable
      
      
      def main(argv: list[str]) -> int:
          p = argparse.ArgumentParser(
              prog="check-rust-facts.py",
              description="Verify rust-ops' version-bearing facts stay cited (offline) and current on crates.io (live).",
          )
          mode = p.add_mutually_exclusive_group()
          mode.add_argument("--offline", action="store_true", help="structural consistency, no network (default)")
          mode.add_argument("--live", action="store_true", help="check every crate's latest major still matches crates.io")
          p.add_argument("--catalog", default=str(DEFAULT_CATALOG), help="facts catalog JSON")
          p.add_argument("--skill", default=str(DEFAULT_SKILL), help="skill directory (SKILL.md + references/)")
          p.add_argument("--timeout", type=float, default=10.0, help="per-request timeout seconds (live)")
          p.add_argument("--json", action="store_true", help="emit a JSON envelope")
          try:
              args = p.parse_args(argv)
          except SystemExit as exc:
              return EX_USAGE if exc.code not in (0, None) else (exc.code or EX_OK)
      
          pkgs, registry = load_catalog(Path(args.catalog))
          live = args.live and not args.offline
          t = Term(sys.stderr)
      
          if live:
              drift, unreachable = check_live(pkgs, registry, args.timeout)
              findings = drift + unreachable
              if args.json:
                  print(json.dumps({
                      "data": findings,
                      "meta": {"mode": "live", "packages_checked": len(pkgs),
                               "drift": len(drift), "unreachable": len(unreachable),
                               "registry": registry, "schema": SCHEMA},
                  }, indent=2))
              else:
                  for f in drift:
                      print(f"DRIFT  {f['package']}: {f['issue']}")
                  for f in unreachable:
                      print(f"UNREACH  {f['package']}: {f['issue']}")
              # §7: confirmed drift -> 10; else transient/unreachable -> 7 (advisory); else 0.
              if drift:
                  eprint(f"{t.mark(False)} rust-facts/live: {len(drift)} crate(s) drifted from documented major "
                         f"{t.c('dim', '(' + registry + ')')}")
                  return EX_DRIFT
              if unreachable:
                  eprint(f"{t.mark(False)} rust-facts/live: crates.io unreachable for "
                         f"{len(unreachable)}/{len(pkgs)} {t.c('dim', '(advisory - retry next run)')}")
                  return EX_UNAVAILABLE
              eprint(f"{t.mark(True)} rust-facts/live: all {len(pkgs)} crate(s) match documented major on crates.io")
              return EX_OK
      
          # offline (default)
          findings = check_offline(pkgs, Path(args.skill))
          if args.json:
              print(json.dumps({
                  "data": findings,
                  "meta": {"mode": "offline", "packages_checked": len(pkgs),
                           "drift": len(findings), "consistent": not findings, "schema": SCHEMA},
              }, indent=2))
          else:
              for f in findings:
                  print(f"DRIFT  {f['package']}: {f['issue']}")
          ok = not findings
          eprint(f"{t.mark(ok)} rust-facts/offline: {len(pkgs)} crate(s) checked, "
                 f"{len(findings)} inconsistency {t.c('dim', '(catalog vs skill prose)')}")
          return EX_DRIFT if findings else EX_OK
      
      
      if __name__ == "__main__":
          sys.exit(main(sys.argv[1:]))
      
  • tests
    • run.sh 5.1 KB
      #!/usr/bin/env bash
      # Self-test for the rust-ops skill.
      #
      # Offline-deterministic (no network, no Rust toolchain required). Asserts
      # structural integrity (frontmatter, references present + cited) and — the
      # load-bearing check — the staleness verifier contract (SKILL-RESOURCE-PROTOCOL
      # §7, §10): the catalogued version-bearing facts (tokio, axum, serde) stay
      # named in the prose and the dated currency note stays present. Resolves paths
      # relative to itself so it works in the repo and once installed to
      # ~/.claude/skills/rust-ops/.
      #
      # Usage:   bash tests/run.sh
      # Exit:    0 all pass, 1 one or more failures
      
      set -uo pipefail
      
      HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
      SKILL="$(dirname "$HERE")"
      DOC="$SKILL/SKILL.md"
      REF="$SKILL/references"
      
      PASS=0; FAIL=0
      ok() { PASS=$((PASS+1)); printf '  PASS  %s\n' "$1"; }
      no() { FAIL=$((FAIL+1)); printf '  FAIL  %s\n' "$1"; }
      has() { case "$2" in *"$1"*) ok "$3";; *) no "$3 (missing '$1')";; esac; }
      
      echo "=== rust-ops self-test ==="
      
      # ── SKILL.md frontmatter ───────────────────────────────────────────────────
      echo "-- frontmatter --"
      [[ -f "$DOC" ]] && ok "SKILL.md present" || { no "SKILL.md missing"; echo "=== $PASS passed, $FAIL failed ==="; exit 1; }
      [[ "$(sed -n '1p' "$DOC")" == "---" ]] && ok "frontmatter fence opens at line 1" || no "no opening frontmatter fence"
      doc="$(cat "$DOC")"
      has 'name: rust-ops'      "$doc" "frontmatter declares name: rust-ops"
      has 'description:'        "$doc" "frontmatter has description"
      has 'license: MIT'        "$doc" "frontmatter declares license"
      has 'author: claude-mods' "$doc" "frontmatter declares metadata.author"
      
      # ── references: the 6 documented files exist ───────────────────────────────
      echo "-- references present --"
      EXPECT=(ownership-lifetimes traits-generics error-handling async-tokio ecosystem testing)
      for r in "${EXPECT[@]}"; do
        f="$REF/$r.md"
        [[ -f "$f" ]] && ok "$r.md present" || no "$r.md missing"
      done
      
      # ── every references/ citation in SKILL.md resolves (no ghost refs) ────────
      # rust-ops cites its references as inline code spans (`./references/x.md`),
      # not markdown links — extract those spans and confirm each file exists.
      echo "-- cited references resolve --"
      linked=0; broken=0
      while IFS= read -r rel; do
        [[ -z "$rel" ]] && continue
        linked=$((linked+1))
        [[ -f "$SKILL/$rel" ]] || { no "SKILL.md cites missing file: $rel"; broken=$((broken+1)); }
      done < <(grep -oE '`(\./)?references/[^`#]+\.md`' "$DOC" | sed -E 's/^`//; s/`$//; s/^\.\///' | sort -u)
      [[ "$linked" -gt 0 ]] && ok "SKILL.md cites its references ($linked unique)" || no "SKILL.md cites no references"
      [[ "$broken" -eq 0 ]] && ok "all cited reference files resolve" || no "$broken reference citation(s) broken"
      
      # ── dated currency note present (verifier depends on it) ───────────────────
      echo "-- currency note --"
      grep -qE 'as of 20[0-9]{2}' "$DOC" && ok "dated 'as of 20XX' currency note present" || no "no dated currency note"
      
      # ── staleness verifier: offline contract (SKILL-RESOURCE-PROTOCOL §7) ───────
      echo "-- check-rust-facts.py (offline) --"
      VERIFIER="$SKILL/scripts/check-rust-facts.py"
      CATALOG="$SKILL/assets/rust-facts.json"
      ec() { local want="$1" lbl="$2"; shift 2; "$@" >/dev/null 2>&1; local got=$?
             [[ "$got" == "$want" ]] && ok "$lbl (exit $got)" || no "$lbl (want $want got $got)"; }
      # Pick a python that actually executes — skips the Windows Store python3 stub.
      PY=""
      for c in python python3 py; do
        if command -v "$c" >/dev/null 2>&1 && "$c" -c "" >/dev/null 2>&1; then PY="$c"; break; fi
      done
      [[ -f "$VERIFIER" ]] && ok "verifier present" || no "verifier missing"
      [[ -f "$CATALOG"  ]] && ok "facts catalog present" || no "catalog missing"
      has "scripts/check-rust-facts.py" "$doc" "verifier cited from SKILL.md"
      if [[ -n "$PY" ]]; then
        TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
        ec 0 "py_compile"            "$PY" -m py_compile "$VERIFIER"
        ec 0 "--help"                "$PY" "$VERIFIER" --help
        ec 0 "--offline consistent"  "$PY" "$VERIFIER" --offline
        ec 2 "bad flag -> 2"         "$PY" "$VERIFIER" --bogus
        ec 2 "conflicting modes -> 2" "$PY" "$VERIFIER" --offline --live
        jout="$("$PY" "$VERIFIER" --offline --json 2>/dev/null)"
        has 'claude-mods.rust-ops.facts/v1' "$jout" "--json envelope schema"
        ec 3 "missing catalog -> 3"  "$PY" "$VERIFIER" --offline --catalog "$TMP/nope.json"
        printf '{"packages":"x"}' > "$TMP/bad.json"
        ec 4 "malformed catalog -> 4" "$PY" "$VERIFIER" --offline --catalog "$TMP/bad.json"
        printf '{"packages":[{"name":"zzznotreal","documented_major":1}]}' > "$TMP/drift.json"
        ec 10 "uncited package -> 10" "$PY" "$VERIFIER" --offline --catalog "$TMP/drift.json"
      else
        no "no working python to exercise the verifier"
      fi
      
      # ── summary ────────────────────────────────────────────────────────────────
      echo "=== $PASS passed, $FAIL failed ==="
      [[ "$FAIL" -eq 0 ]] || exit 1
      
  • SKILL.md 11 KB
    ---
    name: rust-ops
    description: "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."
    license: MIT
    allowed-tools: "Read Write Bash"
    metadata:
      author: claude-mods
      related-skills: docker-ops, ci-cd-ops, testing-ops
    ---
    
    # Rust Operations
    
    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)
    
    ```rust
    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)
    
    ```rust
    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
    
    ```rust
    // ? 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
    
    ```rust
    #[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`) |
    |---|---|---|
    | Dispatch | Dynamic (vtable) | Static (monomorphized) |
    | Binary size | Smaller | Larger (per-type copies) |
    | Performance | Slight overhead | Zero-cost |
    | Heterogeneous collections | Yes | No |
    | Use when | Runtime polymorphism, plugin systems | Performance-critical, known types |
    
    ```rust
    // 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
    
    | Trait | Purpose | Auto-derive? |
    |-------|---------|-------------|
    | `Debug` | Debug formatting | Yes |
    | `Clone` | Explicit copy | Yes |
    | `Copy` | Implicit copy (small, stack-only) | Yes |
    | `Display` | User-facing formatting | No (impl manually) |
    | `From`/`Into` | Type conversion | No (impl `From`, get `Into` free) |
    | `Send` | Safe to send between threads | Auto |
    | `Sync` | Safe to share references between threads | Auto |
    | `Deref` | Smart pointer dereference | No |
    | `Iterator` | Iteration protocol | No |
    | `Default` | Default value | Yes |
    
    **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
    
    ```rust
    #[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
    
    | Channel | Use Case | Import |
    |---------|----------|--------|
    | `mpsc` | Multiple producers, single consumer | `tokio::sync::mpsc` |
    | `oneshot` | Single value, single use | `tokio::sync::oneshot` |
    | `broadcast` | Multiple consumers, all get every message | `tokio::sync::broadcast` |
    | `watch` | Single 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
    
    ```bash
    # 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
    
    ```toml
    [features]
    default = ["json"]
    json = ["dep:serde_json"]
    full = ["json", "yaml", "toml"]
    
    [dependencies]
    serde_json = { version = "1", optional = true }
    ```
    
    ### Release Profile Tuning
    
    ```toml
    [profile.release]
    lto = true            # Link-time optimization: smaller, faster binaries
    codegen-units = 1     # Better optimization at the cost of compile time
    ```
    
    ## Common Gotchas
    
    | Gotcha | Why | Fix |
    |--------|-----|-----|
    | `String` vs `&str` | Owned vs borrowed, function signatures | Accept `&str` in params, return `String` |
    | Borrow checker fight | Borrowing self while mutating | Split struct, use indices, clone (if cheap) |
    | Lifetime elision confusion | Hidden lifetimes in function signatures | Write them out explicitly to understand, then elide |
    | `impl Trait` in return | Different branches must return same type | Use `Box<dyn Trait>` for heterogeneous returns |
    | `tokio::Mutex` vs `std::Mutex` | `std::Mutex` can't be held across `.await` | Use `tokio::Mutex` across await points |
    | Orphan rule | Can't impl foreign trait for foreign type | Newtype pattern: `struct Wrapper(ForeignType)` |
    | `Pin` confusion | Required for self-referential async futures | Use `Box::pin()`, don't fight it |
    | `Send` bounds on async | Spawned futures must be `Send` | Avoid `Rc`, `RefCell` in async; use `Arc`, `Mutex` |
    | `.unwrap()` in production | Panics on None/Err | Use `?`, `.unwrap_or()`, `.expect("reason")` |
    
    ## serde Quick Reference
    
    ```rust
    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.
    
    | Reference | When to Load |
    |-----------|-------------|
    | `./references/ownership-lifetimes.md` | Borrowing rules, lifetime annotations, elision, interior mutability, common borrow checker patterns |
    | `./references/traits-generics.md` | Trait design, associated types, supertraits, generics, constraints, sealed/extension traits |
    | `./references/error-handling.md` | Result/Option combinators, thiserror/anyhow deep dive, error conversion, panic/recover |
    | `./references/async-tokio.md` | tokio runtime, spawn, channels, select, streams, graceful shutdown, async traits, Mutex choice |
    | `./references/ecosystem.md` | serde advanced, clap, reqwest, sqlx, axum, tracing, rayon, itertools, Cow |
    | `./references/testing.md` | Unit/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
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related