| name | rust-patterns |
| description | Rust: ownership, lifetimes, async (Tokio), Result/anyhow/thiserror, traits, unsafe. Triggers: Rust, borrow checker, lifetime, Tokio, cargo, trait, impl, Result, unsafe, clippy. |
| effort | medium |
| user-invocable | false |
| allowed-tools | Read |
Rust Patterns
Project Structure
my-app/
├── Cargo.toml
├── src/
│ ├── main.rs # Binary entry point
│ ├── lib.rs # Library root (re-exports)
│ ├── error.rs # Crate-level error types
│ ├── api/
│ │ ├── mod.rs
│ │ └── handlers.rs
│ └── domain/
│ ├── mod.rs
│ └── service.rs
├── tests/ # Integration tests (separate crate)
│ └── api_test.rs
├── benches/ # criterion benchmarks
│ └── throughput.rs
└── examples/
└── demo.rs
Workspace layout for multi-crate projects:
[workspace]
resolver = "2"
members = ["crates/core", "crates/api", "crates/cli"]
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
Idioms / Code Style
Ownership and Borrowing
fn print_name(name: &str) { println!("{name}"); }
fn register_user(name: String) -> User {
User { name, id: Uuid::new_v4() }
}
Lifetimes
struct Parser<'input> {
source: &'input str,
pos: usize,
}
impl<'input> Parser<'input> {
fn next_token(&mut self) -> Option<&'input str> {
let start = self.pos;
Some(&self.source[start..self.pos])
}
}
Trait-Based Design
trait Repository {
fn find_by_id(&self, id: Uuid) -> Result<Option<User>, DbError>;
fn save(&self, user: &User) -> Result<(), DbError>;
}
fn create_user(repo: &impl Repository, name: String) -> Result<User, AppError> {
let user = User::new(name);
repo.save(&user)?;
Ok(user)
}
Iterators, Pattern Matching, Newtype
let active: Vec<&str> = users.iter()
.filter(|u| u.is_active)
.map(|u| u.email.as_str())
.collect();
match command {
Command::Start { port } => start_server(port),
Command::Stop => shutdown(),
}
let Some(cfg) = load_config() else { return Ok(Config::default()); };
struct UserId(Uuid);
struct Email(String);
impl Email {
fn new(raw: &str) -> Result<Self, ValidationError> {
if raw.contains('@') { Ok(Self(raw.to_lowercase())) }
else { Err(ValidationError::InvalidEmail) }
}
}
Builder Pattern
#[derive(Default)]
struct RequestBuilder { url: String, timeout: Option<Duration> }
impl RequestBuilder {
fn url(mut self, url: impl Into<String>) -> Self { self.url = url.into(); self }
fn timeout(mut self, d: Duration) -> Self { self.timeout = Some(d); self }
fn build(self) -> Result<Request, BuildError> {
if self.url.is_empty() { return Err(BuildError::MissingUrl); }
Ok(Request { url: self.url, timeout: self.timeout.unwrap_or(Duration::from_secs(30)) })
}
}
Error Handling
thiserror (libraries) vs anyhow (binaries)
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error("validation failed: {0}")]
Validation(String),
#[error("database error")]
Database(#[from] sqlx::Error),
#[error(transparent)]
Unexpected(#[from] anyhow::Error),
}
use anyhow::{Context, Result};
fn load_config(path: &Path) -> Result<Config> {
let content = fs::read_to_string(path)
.with_context(|| format!("failed to read {}", path.display()))?;
toml::from_str(&content).context("invalid TOML")
}
Error Propagation and Boundary Mapping
fn get_email(pool: &PgPool, id: Uuid) -> Result<String, AppError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_optional(pool).await?
.ok_or_else(|| AppError::NotFound(format!("user {id}")))?;
Ok(user.email)
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, msg) = match &self {
AppError::NotFound(m) => (StatusCode::NOT_FOUND, m.clone()),
AppError::Validation(m) => (StatusCode::BAD_REQUEST, m.clone()),
_ => (StatusCode::INTERNAL_SERVER_ERROR, "internal error".into()),
};
(status, Json(json!({ "error": msg }))).into_response()
}
}
Testing Patterns
Unit Tests (inline module)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn email_rejects_invalid() { assert!(Email::new("bad").is_err()); }
#[tokio::test]
async fn fetches_user() {
let pool = setup_test_db().await;
let user = get_user(&pool, test_id()).await.unwrap();
assert_eq!(user.name, "Alice");
}
}
Integration Tests (tests/ directory)
#[tokio::test]
async fn health_returns_200() {
let app = my_app::app().await;
let resp = app.oneshot(
Request::builder().uri("/health").body(Body::empty()).unwrap()
).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
Mocking (mockall) and Property Testing (proptest)
use mockall::automock;
#[automock]
trait UserRepo { fn find(&self, id: Uuid) -> Result<Option<User>, DbError>; }
#[test]
fn returns_not_found_when_missing() {
let mut mock = MockUserRepo::new();
mock.expect_find().returning(|_| Ok(None));
assert!(matches!(UserService::new(mock).get_user(id), Err(AppError::NotFound(_))));
}
use proptest::prelude::*;
proptest! {
#[test]
fn roundtrips(s in "[a-z]{1,20}@[a-z]{1,10}\\.[a-z]{2,4}") {
let e = Email::new(&s).unwrap();
assert_eq!(e.to_string(), s.to_lowercase());
}
}
cargo test
cargo test --lib
cargo test --test api_test
cargo nextest run
Common Libraries
| Crate | Purpose | Notes |
|---|
| serde / serde_json | Serialization | #[derive(Serialize, Deserialize)] on DTOs |
| tokio | Async runtime | features = ["full"] for apps |
| axum | HTTP framework | Tower-based, extractors, State |
| clap | CLI parsing | #[derive(Parser)] |
| reqwest | HTTP client | Async, rustls TLS |
| tracing | Structured logging | Replaces log; use tracing-subscriber |
| sqlx | Async SQL | Compile-time checked queries |
| tower | Middleware | Layers, timeouts, rate limiting |
| rayon | Data parallelism | .par_iter() drop-in |
| criterion | Benchmarking | Statistical, HTML reports |
Axum + Tracing Minimal Server
use axum::{extract::State, routing::get, Json, Router};
use std::sync::Arc;
struct AppState { db: PgPool }
async fn list_users(State(s): State<Arc<AppState>>) -> Json<Vec<User>> {
Json(sqlx::query_as!(User, "SELECT * FROM users")
.fetch_all(&s.db).await.unwrap())
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.json().init();
let pool = PgPool::connect(&std::env::var("DATABASE_URL").unwrap()).await.unwrap();
let app = Router::new().route("/users", get(list_users)).with_state(Arc::new(AppState { db: pool }));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Performance Tips
fn names(data: &[Record]) -> Vec<&str> {
data.iter().map(|r| r.name.as_str()).collect()
}
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') { Cow::Owned(input.replace(' ', "_")) }
else { Cow::Borrowed(input) }
}
use rayon::prelude::*;
let out: Vec<_> = inputs.par_iter().map(|i| compute(i)).collect();
let mut v = Vec::with_capacity(items.len());
let sum: u64 = values.iter().filter(|v| **v > 0).sum();
Benchmarking (criterion)
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn bench_parse(c: &mut Criterion) {
let input = include_str!("../fixtures/large.json");
c.bench_function("parse_json", |b| b.iter(|| parse(black_box(input))));
}
criterion_group!(benches, bench_parse);
criterion_main!(benches);
Build / Package Management
Cargo.toml
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
mockall = "0.13"
[[bench]]
name = "throughput"
harness = false
Feature Flags
[features]
default = ["json"]
json = ["serde_json"]
postgres = ["sqlx/postgres"]
#[cfg(feature = "postgres")]
pub mod pg_backend;
Release Profile
[profile.release]
lto = true
codegen-units = 1
strip = true
panic = "abort"
[profile.dev.package."*"]
opt-level = 2
Cross-Compilation
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
cargo install cross && cross build --release --target aarch64-unknown-linux-gnu
Essential Cargo Commands
cargo clippy -- -D warnings
cargo fmt --check
cargo doc --open
cargo tree -d
cargo audit
cargo deny check
cargo bloat --release
CI Pipeline (minimum)
cargo fmt --check && cargo clippy -- -D warnings && cargo test && cargo audit
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|
.unwrap() in lib code | Panics | Return Result/Option |
.clone() everywhere | Hidden allocs | Borrow, Cow, restructure ownership |
String in every field | Unnecessary allocs | &str or Cow<str> where lifetime allows |
Box<dyn Error> only | No matching | thiserror enums |
Arc<Mutex<T>> first | Contention | Channels (tokio::sync::mpsc) or actors |
Ignoring #[must_use] | Silent drops | Handle or let _ = explicitly |