← headroom.walls.sh

Claude Code for Rust

Rust's compiler is unusually helpful — it names the exact variable, lifetime, and rule violated in every error. Claude Code reads those errors precisely and fixes them iteratively without guessing. This page covers the patterns that work best.

CLAUDE.md for a Rust project

# Rust Project

## Commands
- Build: cargo build
- Test: cargo test
- Check (fast): cargo check
- Lint: cargo clippy -- -D warnings
- Format: cargo fmt
- Bench: cargo bench

## Conventions
- No unwrap() or expect() in library code — use ? or proper error types
- Error types implement std::error::Error via thiserror
- No clone() to fix borrow issues — fix the borrow instead
- All public API has doc comments (///)
- Tests in the same file (mod tests at the bottom) for unit tests
- Integration tests in tests/ directory

The "no clone() to fix borrow issues" rule is important — without it, Claude Code may take the easy path of cloning to silence the borrow checker instead of finding the correct ownership design.

Fix borrow checker errors

claude "this code has a borrow checker error: [paste the rustc error + the relevant code]. Fix it correctly — don't add clone() unless cloning is semantically correct here. Explain what ownership rule was violated and why the fix works."

Rust's borrow checker errors are structured and precise — Claude Code reads them well. The key is asking for the explanation: it forces a diagnosis rather than a mechanical fix that might hide the real issue.

For a wall of errors after a refactor:

claude "cargo check produces these errors: [paste output]. Fix them all. Don't use unwrap(), clone(), or unsafe to paper over issues. Run cargo check after each fix to verify the count goes down."

Resolve lifetime errors

claude "this function has a lifetime error. Read the function signature, the struct it returns, and the callers. Explain which lifetime should be named and why, then add the annotations."

Lifetime errors are harder than borrow errors because they often require understanding the caller context, not just the function in isolation. Telling Claude Code to read the callers first leads to better lifetime designs than letting it annotate in isolation.

claude "I'm getting 'lifetime may not live long enough' in this async function. The issue is likely the Future's lifetime bounds. Read the function and the trait it implements, then fix the lifetime annotations."

Clippy fix loop

claude "run cargo clippy -- -D warnings and fix every warning. No #[allow(clippy::...)] suppressions unless the lint is genuinely wrong for this case — explain why if you suppress one. Run clippy again after each batch of fixes."

Clippy is one of the best uses of Claude Code in Rust — there are often 20–50 warnings in a new codebase, and fixing them mechanically is tedious but not hard. Claude Code works through them systematically.

Error handling with thiserror

claude "replace all the string error returns (Err("something failed".to_string())) in src/parser.rs with a proper ParseError enum using thiserror. Keep the same error messages but as enum variants with fields. Update all call sites to use the new type."
claude "add a top-level AppError enum with thiserror that wraps ParseError, IoError (from std::io::Error), and NetworkError. Implement From for each. Update main.rs to use ? throughout instead of match on errors."

Write tests

claude "write unit tests for the parse_config function in src/config.rs. Cover: valid input, missing required field, invalid type, empty input, unicode in string fields. Use the mod tests pattern at the bottom of the file."
claude "write integration tests in tests/parser_integration.rs. Test the full parse → validate → execute pipeline with real input files from tests/fixtures/. Create the fixture files as part of this."

Rust's built-in test runner is fast — Claude Code can write tests and run them in the same session without any setup. The #[cfg(test)] + mod tests pattern is idiomatic; use the tests/ directory for integration tests that need a full binary.

Traits and generics

claude "extract a Serializer trait from the three serializers in src/serializers/. They all have the same methods: serialize(&self, value: &T) -> Vec<u8> and deserialize(&self, bytes: &[u8]) -> Result<T, SerializeError>. Make the existing implementations use the trait. Add a test that works against the trait object."
claude "this function takes a Vec<ConcreteType> but should work with any iterator of items that implement Display. Refactor it to use generics with the right trait bounds."

Async Rust

claude "convert the synchronous file-processing pipeline in src/pipeline.rs to async using tokio. Use tokio::fs for file I/O and tokio::spawn for the parallel processing steps. Add the #[tokio::main] attribute and tokio dev-dependency."
claude "this async function holds a MutexGuard across an .await point. That's a compile error because MutexGuard isn't Send. Read the function and restructure it so the guard is dropped before the await."

The MutexGuard-across-await error is one of the most common async Rust pitfalls — Claude Code knows the pattern and fixes it by restructuring the code into a smaller critical section rather than by reaching for unsafe or tokio::sync::Mutex as a first resort.

Performance and unsafe

claude "profile this hot path with criterion: [paste the function]. Write a benchmark in benches/. Identify the bottleneck from the output and propose a fix — explain the tradeoff between safety and performance before touching unsafe."
claude "this function uses unsafe for a raw pointer operation. Read the safety invariants, add a SAFETY comment explaining why the code is correct, and add a test that would catch a violation if the invariant breaks."

Cargo and dependencies

claude "audit Cargo.toml for unused dependencies. Run cargo +nightly udeps or check with cargo tree. Remove unused ones and verify cargo test still passes."
claude "update all dependencies to their latest compatible versions. Run cargo update, then cargo test. If anything breaks, identify which upgrade caused the failure and downgrade just that one."

Monitor session budget during Rust work

Rust compile cycles are slow relative to other languages — cargo check is fast, but full cargo build or cargo test can take 10–30s for medium projects. A clippy fix loop that iterates 15 times burns session budget faster than it appears.

Headroom — track your session budget during Rust compile loops

When Claude Code is iterating on borrow checker fixes or running a clippy loop, your 5-hour session meter is moving. Headroom shows your Claude Code session (5h) and weekly (7d) utilization live in the macOS menu bar — color-coded from calm to amber to red. No token, no API key: it reads the file Claude Code writes to ~/.claude/.

Install in one line:

brew install patwalls/tap/headroom

Start a Rust refactor, glance at the menu bar — you'll know if you have the headroom to finish the error migration before the session resets.

Common Rust + Claude Code patterns

TaskPrompt pattern
Borrow error[paste rustc error + code]. Fix without clone() unless semantically right. Explain the rule.
Clippy loopcargo clippy -D warnings, fix all. No allows without explanation. Run after each batch.
Error typesreplace string errors in [file] with [Name]Error enum using thiserror. Update call sites.
Testswrite unit tests for [function]. Cover [cases]. Use mod tests pattern.
Async fixMutexGuard across await in [function] — restructure so guard drops before the await point.

Claude Code for Go
Writing tests with Claude Code
Claude Code + Zed Editor
Debugging with Claude Code