Rust
Rust rules and best practices for Claude Code.
7.4k
- Rust
- Tokio
- Axum
- Serde
- Sqlx
- Wasm
5 TEMPLATES
Modern Rust Rules
# CLAUDE.md — Modern Rust ## Edition & toolchain - Use the latest stable Rust edition. Set `edition = "2024"` (or current) in `Cargo.toml`. - Pin the toolchain in `rust-toolchain.toml` for reproducibility. - `cargo fmt`, `cargo clippy --all-targets --all-features -- -D warnings` in CI. No exceptions. ## Project layout ``` Cargo.toml src/ lib.rs # public API main.rs # binary, if any error.rs # crate-level errors domain/ infra/ tests/ # integration tests benches/ ``` - For multi-crate projects, use a workspace with members. Keep crates focused — one responsibility each. ## Ownership & borrowing - Default to **owned** types in function signatures (`String`, `Vec<T>`) when the function takes ownership; **slices** (`&str`, `&[T]`) when it borrows. Don't take `&String` or `&Vec<T>`. - Prefer iterators over indexed loops. They're easier for the borrow checker and the compiler. - Lifetimes: elide when possible. Name them only when the compiler asks for it. ## Errors - Two error styles for two contexts: - **Library**: a typed error enum with `thiserror`. Each variant tells the caller exactly what went wrong. - **Application**: `anyhow::Result<T>` for "I just want to bubble errors up with context". - Add context with `.context(...)` (anyhow) or `.map_err(|e| MyError::Foo { source: e })` (thiserror). - Use `?` for propagation. Don't `unwrap` or `expect` outside tests and `main` — even `main` should bubble errors with `Result`. ## Structs & traits - Derive aggressively when it makes sense: `Debug`, `Clone`, `PartialEq`, `Eq`, `Hash`. Don't derive `Default` blindly — it constructs an "empty" value that may not be valid. - Builder pattern for structs with many optional fields. Use `bon` or `derive_builder` to avoid boilerplate. - Don't write trait objects (`Box<dyn Trait>`) when generics work. Reach for `dyn` only when you genuinely need heterogeneous types. ## Modules - One module per file. The directory pattern (`mod.rs` → `module.rs` + folder) is preferred over the older single-file form for anything beyond trivial. - `pub` is opt-in. Most items stay private. - Re-export via `pub use` to flatten the public API. Consumers shouldn't have to know your internal layout. ## Cargo features - Use features for optional functionality, not for two ways of doing the same thing. - Document features in `Cargo.toml` and `README`. - `default-features = false` when consuming a dep — explicit > implicit. ## Performance - Profile with `cargo flamegraph` or `samply` before optimizing. - Avoid unnecessary allocations in hot loops. Reach for `&str` over `String` when you can. - `cargo bench` with **criterion** for serious benchmarks. ## Don't - Don't use `unwrap()` outside tests, examples, and prototypes. Even there, prefer `expect("explanation")`. - Don't use `Rc<RefCell<T>>` to "fix" a borrow-checker problem. Redesign the ownership. - Don't add `unsafe` without a comment explaining the safety invariant. - Don't write `.clone()` to silence a borrow error. Understand what's actually moving.Rust + Axum Web Server
# CLAUDE.md — Rust + Axum Web Server ## Stack - **axum** for routing + handlers, **tokio** for async runtime, **tower** + **tower-http** for middleware. - **tracing** for logs (with `tracing-subscriber` for setup). - **sqlx** or **sea-orm** for the database. Pick one. ## Server skeleton ```rust let state = AppState::new().await?; let app = Router::new() .route("/users", get(list_users).post(create_user)) .route("/users/:id", get(get_user)) .layer(TraceLayer::new_for_http()) .layer(CorsLayer::permissive()) // tighten in prod .with_state(state); let listener = TcpListener::bind("0.0.0.0:8000").await?; axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await?; ``` - Always use `with_graceful_shutdown`. Listen for `SIGTERM`/`SIGINT` and drain in-flight requests. ## Handlers - A handler is `async fn` that returns something implementing `IntoResponse`. - Extractors do the parsing — `Path`, `Query`, `Json`, `State`, custom `FromRequest` impls. - Return `Result<Json<T>, AppError>`. Map your error to a status via `IntoResponse`. ## State - Build a single `AppState` struct that holds the DB pool, config, http client, etc. - Wrap shared mutable state in `Arc<Mutex<...>>` only when you've thought it through. Most of your state is immutable. - Inject with `State<AppState>`. Don't reach for globals. ## Errors ```rust #[derive(thiserror::Error, Debug)] pub enum AppError { #[error("not found")] NotFound, #[error("validation: {0}")] Validation(String), #[error(transparent)] Other(#[from] anyhow::Error), } impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, msg) = match self { AppError::NotFound => (StatusCode::NOT_FOUND, "not found"), AppError::Validation(_) => (StatusCode::BAD_REQUEST, "bad request"), AppError::Other(_) => (StatusCode::INTERNAL_SERVER_ERROR, "internal"), }; (status, msg).into_response() } } ``` - Log the underlying error server-side. Never echo internal errors to clients. ## Middleware - Use `tower-http` for the standard set: tracing, CORS, compression, body limits, request id. - Order: outer layers run first on request, last on response. The convention is: trace → request id → CORS → body limit → app. - Custom middleware: implement `tower::Layer` + `tower::Service`, or use `axum::middleware::from_fn` for one-offs. ## Validation - Use **garde** or **validator** for declarative input validation. - Validate immediately after extraction. Return 400 with field-level errors. ## Testing - `axum::Router` is testable directly. Use `tower::ServiceExt::oneshot`: ```rust let response = app.oneshot(Request::get("/users/123").body(Body::empty())?).await?; assert_eq!(response.status(), StatusCode::OK); ``` - For integration tests, start a real DB (testcontainers) and a real listener on port 0. ## Don't - Don't share a single `Arc<Mutex<DbPool>>`. Pools are already concurrent — wrap once and clone the `Arc`. - Don't block in async handlers. Use `tokio::task::spawn_blocking` for CPU work. - Don't return `Result<impl IntoResponse, anyhow::Error>` directly to clients — `anyhow` is for app code; use a typed error at the boundary. - Don't enable `CorsLayer::permissive()` in production.Rust + Tokio Async Patterns
# CLAUDE.md — Rust + Tokio Async ## Setup - `#[tokio::main]` on `main`. For libraries, accept a runtime handle from the caller — never assume one. - Pin the runtime version in `Cargo.toml`. Major versions break compatibility. - Default to the multi-thread runtime. Use the current-thread runtime only when you need single-thread guarantees. ## Tasks - `tokio::spawn(async move { ... })` schedules a task on the runtime. Returns a `JoinHandle<T>`. - Don't fire-and-forget unless you genuinely don't care. Hold the handle and `await` it, or `abort` it intentionally. - Use `tokio::task::JoinSet` for fan-out: ```rust let mut set = JoinSet::new(); for item in items { set.spawn(work(item)); } while let Some(res) = set.join_next().await { ... } ``` ## Cancellation - Cancellation is cooperative. Tasks are dropped at the next `await` point. - Use `tokio::select!` to race futures, including a cancellation token: ```rust tokio::select! { res = work() => handle(res), _ = cancel.cancelled() => break, } ``` - `tokio_util::sync::CancellationToken` for structured cancellation across many tasks. ## Channels - `tokio::sync::mpsc` (multi-producer, single-consumer) — most common. - `tokio::sync::broadcast` for fan-out to multiple subscribers. - `tokio::sync::oneshot` for "one-shot" reply patterns (request → response). - `watch` for "current value" semantics where late receivers should see the latest. ## Sync primitives - `tokio::sync::Mutex` — async-aware. `std::sync::Mutex` is fine when held briefly and not across `.await`. - `RwLock` only when reads vastly outnumber writes. Otherwise `Mutex` is simpler. - Don't hold any lock across `.await`. Either drop it explicitly (`drop(guard)`) or restructure. ## Timing - `tokio::time::sleep` and `interval` for delays. Never `std::thread::sleep` in async — it blocks the runtime. - `tokio::time::timeout(duration, fut)` to bound any await. - `Instant::now` is fine in async code; the deltas are correct. ## Blocking work - CPU-bound or sync I/O work goes in `tokio::task::spawn_blocking`. Returns a `JoinHandle<T>`. - The blocking pool has a separate thread budget. Don't dump unlimited blocking work — it'll stall. - For "blocking" libraries you can't avoid, wrap them in a small `spawn_blocking` shim. ## Streams - `tokio_stream` and `futures::Stream` for async iteration. - `StreamExt::buffered(n)` to bound concurrency in pipelines. - Don't collect a stream into a `Vec` if you don't have to. Keep it streaming. ## Don't - Don't call `.block_on(...)` from inside an async function. That's how you create deadlocks. - Don't share a tokio Mutex across `.await` if a `parking_lot::Mutex` would do — `parking_lot` is faster but synchronous. - Don't swallow `JoinError`. It carries panic info. - Don't use `Rc` or `RefCell` across tasks. They're not `Send`. Use `Arc` + `Mutex`.Rust CLI Tools (clap)
# CLAUDE.md — Rust CLI Tools (clap) ## Setup - **clap** with the `derive` feature. `cargo add clap --features derive`. - **anyhow** for error context, **thiserror** if your CLI is also a library. - **tracing** + **tracing-subscriber** for structured logs / verbose output. - **indicatif** for progress bars; **dialoguer** for interactive prompts. ## Argument parsing ```rust #[derive(Parser, Debug)] #[command(name = "yourcli", version, about = "...", long_about = None)] struct Cli { #[command(subcommand)] command: Commands, /// Increase verbosity (-v, -vv, -vvv) #[arg(short, long, action = clap::ArgAction::Count, global = true)] verbose: u8, } #[derive(Subcommand, Debug)] enum Commands { Init { #[arg(long)] force: bool }, Deploy { target: String }, } ``` - One enum variant per command. Sub-subcommands nest as their own enum. - `global = true` on flags that apply to every subcommand (verbosity, config path). - Required arguments are positional; optional are `--flags`. ## Help text - Doc comments on fields become `--help` text. Write them. - Use `value_name = "..."` for clearer help on values: `<TARGET>` instead of `<target>`. - Group related flags with `next_help_heading`. ## Output - Default stdout is "the data you piped in / out of". Logs and progress go to stderr. - Use **owo-colors** for color output, gated on `is-terminal` so piped output stays plain. - Honor `NO_COLOR` env var. Most color crates do this automatically. ## Errors - Return `anyhow::Result<()>` from `main`. `anyhow`'s `Display` includes the chain. - Use `.context("doing X")` to add layered context to errors. - For known failure modes (file not found, conflict), exit with a specific code via `std::process::exit(2)` after printing. ## Configuration order - Flag → env var → config file → default. - For env vars, clap's `env = "VAR"` attribute. For config files, **figment** or **config** crates parse YAML/TOML/JSON. ## Logging - Map `-v` count to log level: 0 = warn, 1 = info, 2 = debug, 3+ = trace. - `tracing-subscriber::fmt().with_env_filter(...)` reads `RUST_LOG` for fine-grained filters. - Don't `println!` for log output. Use `tracing::info!` and friends. ## Testing - Library half is tested normally. Binary half via **assert_cmd**: ```rust Command::cargo_bin("yourcli")? .args(["init", "--force"]) .assert() .success() .stdout(predicate::str::contains("ok")); ``` - Snapshot tests for help output with **insta**. ## Distribution - `cargo install yourcli` from crates.io. - For binaries, **cargo-dist** generates GitHub Actions to build and publish per-target. - Static-link with **musl** target on Linux for portable single binaries. ## Don't - Don't print plain `{:?}` of an error for users. Format the chain. - Don't write CLI logic into `main`. `main` does setup and calls into a `run(args) -> Result<()>`. - Don't silently consume `--help` or `--version`. Let clap handle them. - Don't ship interactive prompts with no `--yes` / `--non-interactive` opt-out.Rust Testing Patterns
# CLAUDE.md — Rust Testing Patterns ## Test layout - **Unit tests** live in the same file as the code, in a `#[cfg(test)] mod tests { ... }` block. - **Integration tests** live in `tests/` at the crate root. Each file is a separate binary. - **Doc tests** live in doc comments, run by `cargo test`. - **Benchmarks** in `benches/` with **criterion** for stable measurements. ## Running - `cargo test` runs everything. `cargo test --lib` for unit tests only. - `cargo test <pattern>` filters by name. Useful when iterating. - `cargo nextest run` is faster than `cargo test` for medium-to-large suites. Worth installing. ## Assertions - `assert!`, `assert_eq!`, `assert_ne!` from std are enough most of the time. - For richer output, **pretty_assertions** crate — `pretty_assertions::assert_eq!` shows colored diffs. - For complex shapes, **insta** for snapshot tests: ```rust insta::assert_yaml_snapshot!(result); ``` ## Test fixtures - For shared setup, write helper functions in a `tests/common/mod.rs` (not `tests/common.rs` — that runs as a test binary). - Don't share mutable state across tests. They run in parallel by default. ## Async tests - `#[tokio::test]` on `async fn` test functions. Needs the `tokio` test feature: `tokio = { ..., features = ["macros", "rt-multi-thread"] }`. - For multi-thread runtime: `#[tokio::test(flavor = "multi_thread")]`. - `#[tokio::test(start_paused = true)]` to control time deterministically. ## Property tests - **proptest** or **quickcheck** for generated inputs: ```rust proptest! { #[test] fn parse_roundtrip(s in "[a-z]+") { assert_eq!(parse(&serialize(&s)).unwrap(), s); } } ``` - Property tests catch edge cases unit tests miss. Use them for parsers, serializers, math. ## Mocking - **mockall** for mock implementations of traits. Generate from `#[automock]`. - Don't mock concrete types — refactor to traits at the boundary, then mock those. - For HTTP, use **wiremock** or **mockito** to stand up a fake server in-process. ## Database tests - Use **testcontainers-rs** to spin up real Postgres/Redis/whatever. The boot cost is amortized over many tests. - Each test creates its own schema or uses a transaction that rolls back at the end. - Don't fake the database. Bugs hide in the impedance. ## Coverage - `cargo llvm-cov` (or `cargo tarpaulin`) for coverage. Aim for high coverage on logic, not on every match arm. - Coverage targets are signals, not goals. A 95% covered codebase with no integration tests has worse properties than 70% covered with good ones. ## CI - `cargo test --all-features` and `cargo test --no-default-features` separately. Feature combinations are where bugs hide. - `cargo clippy --all-targets --all-features -- -D warnings` is mandatory. - Cache `target/` between runs. Rust builds are slow without it. ## Don't - Don't `.unwrap()` everything in tests. `expect("explanation")` so failures point at the cause. - Don't sleep in tests for timing. Use mocked time or signals. - Don't depend on test execution order. They run in parallel; assume any order. - Don't disable warnings to make a test compile. Fix the warning.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.