Claude Code for Rust — Complete Setup & Workflow Guide
Claude Code works exceptionally well with Rust when configured to understand your Cargo workspace, error handling strategy, and async runtime. This guide covers the CLAUDE.md template, automated cargo test and clippy hooks, idiomatic Rust configuration, and workflow patterns for async services, CLI tools, and WebAssembly targets.
Rust CLAUDE.md Template
# Project: [Your Crate / Service Name]
## Stack
- Rust edition 2021
- Cargo workspace: [yes/no — if yes, list member crates]
- Async runtime: tokio 1.x with full features
- Error handling: thiserror (library), anyhow (binary / tests)
- HTTP: axum 0.7 (or actix-web, warp — specify)
- Database: sqlx with PostgreSQL (or diesel, sea-orm — specify)
- Serialization: serde + serde_json, serde_yaml
## Commands
```bash
cargo build # compile check
cargo test # run all tests (unit + integration)
cargo test --lib # unit tests only (faster TDD loop)
cargo test -- --nocapture # show println! output in tests
cargo clippy -- -D warnings # lint (treat warnings as errors)
cargo fmt --check # format check
cargo doc --open # build docs
```
## Conventions
- Errors: use thiserror for library error enums, anyhow::Result in binaries
- Never use unwrap() in library code; use ? or map_err
- Async: #[tokio::main] for binaries; tokio::spawn for background tasks
- Shared state: prefer message passing (tokio::sync::mpsc) over Arc<Mutex<T>>
- Traits: define in the crate that owns the abstraction
- Tests: unit tests in the same file (mod tests {}); integration tests in tests/
- Lifetimes: prefer owned types in public APIs unless performance-critical
## Project structure
- src/
- main.rs or lib.rs — entry point
- error.rs — thiserror error types
- config.rs — Config struct + from_env()
- handlers/ — HTTP handlers (axum)
- services/ — business logic
- db/ — database queries (sqlx)
- models/ — domain types
- tests/ — integration tests
- benches/ — criterion benchmarks
Automated cargo test + clippy Hooks
Run tests and clippy after every edit so Claude sees failures immediately:
// .claude/settings.json
{
"allowedTools": ["Edit", "Write", "Bash", "Read", "Glob", "Grep"],
"hooks": [
{
"event": "PostToolUse",
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "cd $PROJECT_ROOT && cargo test --lib 2>&1 | tail -30"
}]
}
]
}
Use
cargo test --lib during active development for sub-second feedback on unit tests. Switch to cargo test (full suite including integration tests) before finalizing a feature.Clippy hook (catch lint errors immediately)
{
"event": "PostToolUse",
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "cd $PROJECT_ROOT && cargo clippy -- -D warnings 2>&1 | head -40"
}]
}
Format check hook
{
"event": "PostToolUse",
"matcher": "Write",
"hooks": [{
"type": "command",
"command": "cd $PROJECT_ROOT && cargo fmt --check 2>&1 | head -20"
}]
}
Teaching Claude Idiomatic Rust
Specify your Rust conventions in CLAUDE.md so Claude generates code that matches your codebase's style:
| Pattern | CLAUDE.md instruction |
|---|---|
| Error handling | "Use thiserror in lib.rs; anyhow::Result in main.rs and tests" |
| No unwrap | "Never call .unwrap() or .expect() in library code; always propagate with ?" |
| Async state sharing | "Prefer tokio::sync::mpsc over Arc<Mutex<T>> for mutable shared state" |
| Trait objects | "Use Box<dyn Trait> only when object safety required; prefer generics otherwise" |
| Builder pattern | "Use the builder pattern for structs with 3+ optional fields" |
| Test structure | "Unit tests in #[cfg(test)] mod tests {} at bottom of each file" |
Async / Tokio Workflows
HTTP service endpoint (axum)
claude "add a POST /api/users endpoint to the axum router.
Handler in src/handlers/users.rs.
Validate request body with serde (use #[serde(deny_unknown_fields)]).
Call UserService::create_user() in src/services/users.rs.
Insert via sqlx query in src/db/users.rs.
Return 201 Created with the created user as JSON.
Use AppError for error responses.
Write unit tests for the handler and service layers."
Background task with tokio::spawn
claude "add a background cleanup task that runs every 15 minutes.
Use tokio::time::interval. Task: delete rows older than 30 days from the events table.
Run it in main() with tokio::spawn. Propagate a CancellationToken for graceful shutdown.
Log each run with tracing::info!."
Concurrent fetch with JoinSet
claude "rewrite fetch_user_profile to concurrently fetch user, posts, and followers.
Use tokio::task::JoinSet. Cancel all tasks if any returns an error.
Propagate ctx: CancellationToken. Keep the function signature the same."
CLI Tool Workflows (clap)
# CLAUDE.md additions for CLI tools
## CLI Framework
- clap 4.x with derive macros (#[derive(Parser)])
- Subcommands as separate structs in src/commands/
- Config: figment (config.toml + env vars)
- Output: text to stdout, errors to stderr via eprintln!
- Progress: indicatif for progress bars
## Commands
cargo run -- [subcommand] [flags] # test locally
cargo build --release # optimized binary
claude "add a 'sync' subcommand.
Usage: tool sync --source <dir> --dest <s3-bucket> [--dry-run].
Use clap derive. Progress bar with indicatif.
Dry-run prints what would be uploaded without uploading.
Unit test the file-collection logic in tests/."
WebAssembly / wasm-bindgen Workflows
# CLAUDE.md additions for Wasm targets
## Wasm build
- wasm-bindgen 0.2
- wasm-pack for build toolchain (wasm-pack build --target web)
- No std I/O in Wasm-facing code; use web_sys for DOM
- Feature flags: [features] = ["wasm"] gated with #[cfg(target_arch = "wasm32")]
## Test
wasm-pack test --headless --firefox # browser tests
5 Tips for Rust + Claude Code
- Share your
Cargo.tomlcontents in CLAUDE.md under a "Dependencies" section. Claude will use the exact crate versions you have and won't invent incompatible APIs. - Tell Claude your MSRV (minimum supported Rust version) if you have one: "MSRV: 1.70". Claude will avoid features from later editions like
LazyLock(1.80) orasync fn in traits(stable in 1.75). - For generated code (sqlx queries, protobuf, derive macros), tell Claude: "Never edit files in src/generated/ — those are sqlx output." Claude will regenerate via cargo sqlx prepare instead of editing directly.
- When Claude introduces a lifetime parameter, ask it to explain the constraint. This creates a mini-review cycle that catches subtle borrow checker issues before they compound.
- For workspace projects, run
cargo test --workspacein your hook to catch cross-crate regressions. The extra compile time is worth it for workspace-level invariants.