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, axum, sqlx, reqwest, rayon, tracing.
Best use case
rust-ops is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
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.
Teams using rust-ops should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/rust-ops/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How rust-ops Compares
| Feature / Agent | rust-ops | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
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.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# 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
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 }
```
## 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