rust-ops
SKILL.md
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<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
- At any time, you can have either one
&mut Tor any number of&T - References must always be valid (no dangling)
- 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 }
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-compilationtesting-ops- Cross-language testing strategies
Weekly Installs
1
Repository
0xdarkmatter/claude-modsGitHub Stars
10
First Seen
7 days ago
Security Audits
Installed on
zencoder1
amp1
cline1
openclaw1
opencode1
cursor1