--- 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. ## 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 (atomic reference counting) │ └─ Need mutation too? Arc> │ ├─ Need to share ownership single-threaded │ └─ Rc (reference counting, not Send) │ └─ Need mutation too? Rc> │ └─ 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: 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, .context("msg") │ ├─ Converting between error types │ └─ impl From 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 { 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(item: T) { println!("{item}"); } // Trait objects (when you need heterogeneous collections) fn process_all(items: &[Box]) { 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 } ``` ## 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` 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, #[serde(default)] is_active: bool, #[serde(rename = "type")] user_type: UserType, #[serde(with = "chrono::serde::ts_seconds")] created_at: DateTime, } // 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