Covers both dev paths (plain cargo vs nix develop/build/flake check), LSP setup via the fenix toolchain's RUST_SRC_PATH, how nix/rust.nix is shared between the package build and dev shell, where to add unit vs integration tests, and how to use the rename script. Includes a note to keep it updated as the template grows into real functionality. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ALEXkc7pTro7tF1WcUuKb
5.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
A minimal Rust project template: a clap-based CLI (src/main.rs) backed by a library crate
(src/lib.rs), with unit tests, an integration test, and a criterion benchmark already wired
up. It also carries a full Nix flake (crane + fenix) so the same build/test/lint pipeline runs
identically with plain cargo or with nix. The intent is to eventually turn this into a nix flake init template — see scripts/rename-project.sh.
Two ways to work in this repo
Cargo way — fastest inner loop, needs Rust installed locally (or run inside nix develop):
cargo run -- Zeke # build + run the CLI, arg is the name to greet (default: "world")
cargo build # debug build
cargo build --release
cargo test # unit test (src/lib.rs) + integration test (tests/greet.rs)
cargo bench # criterion benchmark (benches/greet.rs)
cargo clippy --all-targets -- --deny warnings
cargo fmt
To run a single test: cargo test greets_by_name (matches both the unit and integration test by
name; scope to one with cargo test --test greet for just the integration test, or
cargo test --lib for just the unit test).
Nix way — reproducible, pins the exact toolchain via fenix, no local Rust install required:
nix develop # drops into a shell with the same toolchain/tools as CI, then use cargo as above
nix build # produces ./result/bin/rust_template
nix run # build + run the CLI
nix flake check # runs every check below in parallel, each cached separately by crane
nix fmt # formats Rust (rustfmt), Nix (nixfmt), and TOML (taplo) via treefmt
nix flake check runs: clippy (deny warnings, all targets), fmt (rustfmt), doc (cargo doc,
deny warnings), audit (cargo-audit against the RustSec advisory DB), nextest (unit +
integration tests), and formatting (treefmt check). These are the same derivations
nix develop's dev shell inherits, so a passing nix flake check is a reliable signal before
pushing.
Both paths are meant to converge on the same result — the Nix devShell and the Nix package build
are built from one shared toolchain/build-args definition (see Architecture below), so what
compiles under nix build should also compile under plain cargo build inside nix develop.
IDE / LSP support
rust-analyzer is included in the fenix toolchain (nix/rust.nix), and nix develop's
shellHook exports RUST_SRC_PATH so rust-analyzer can resolve std-library sources. Point your
editor's rust-analyzer at the toolchain from inside nix develop (e.g. via direnv + use flake,
or by launching your editor from within nix develop) rather than relying on a system-wide Rust
install, so the LSP sees the same compiler version as the build.
Architecture
nix/rust.nixis the single source of truth for the Rust build environment: the fenix toolchain,craneLib,commonArgs(source filtering,strictDeps, native/build inputs), and the cachedcargoArtifacts(deps built once viacraneLib.buildDepsOnly, then reused by the package build, clippy, doc, and nextest checks — this is what makesnix buildincremental instead of recompiling all dependencies on every source change).packages.defaultanddevShells.defaultboth derive from this file's output, which is why they stay close to identical.nix/devshell.nixbuilds the dev shell from that samecraneLib/toolchain viacraneLib.devShell, adding dev-only tools (cargo-nextest,cargo-criterion,cargo-audit,cargo-expand,taplo,bacon,moldon Linux) that aren't part of the package build itself.nix/treefmt.nixis a flake-parts module enabling rustfmt/nixfmt/deadnix/statix/taplo under treefmt-nix, wired into bothnix fmtand theformattingcheck inflake.nix.flake.nixties it together withflake-parts: it imports the treefmt module, evaluatesnix/rust.nixper-system, and exposespackages.default,checks,apps.default, anddevShells.defaultfrom its outputs.src/lib.rsholds the actual logic (currently justgreet);src/main.rsis a thinclapwrapper around it. Keep new functionality in the lib crate, notmain.rs, so it stays reachable fromtests/andbenches/(both depend on therust_templatelib crate by name, not on the binary).tests/greet.rsis the example integration test (tests the public lib API from outside the crate);src/lib.rs's#[cfg(test)] mod testsis the example unit test (tests internals in-crate). Follow whichever pattern fits: internal/private logic → unit test in the same file; public API surface → integration test intests/.benches/greet.rsis acriterionbenchmark against the lib crate;Cargo.tomldisables the default libtest harness for it (harness = false) since criterion supplies its own.
Renaming the template
scripts/rename-project.sh <new_snake_case_name> replaces every rust_template occurrence
(crate name in Cargo.toml, the pname/binary name in nix/rust.nix and flake.nix, and the
use rust_template::... imports in src/main.rs, tests/greet.rs, benches/greet.rs), then
regenerates Cargo.lock. It does not rename the project directory itself — that's a manual mv
afterward (the script prints the exact command). Run nix flake check after renaming to confirm
everything still resolves.
Keeping this file current
This file describes the template in its current, minimal state. As real functionality gets added
(more crates, workspace layout, new checks, CI, actual product logic replacing greet), update
this file to match — stale architecture notes are worse than none. Don't let it drift from what
scripts/rename-project.sh actually touches, either, if the file list it edits changes.