Files
rust_template/CLAUDE.md
T
tomatocream c7303154cc Add flake.templates, justfile, and .envrc for direnv
Compared this template against the older nix-templates/templates/rust
("rrrr") ahead of eventually replacing it. Ported the pieces that were
genuinely missing and non-controversial:

- flake.templates.default, so `nix flake init -t <url>` works once
  this repo is published — the actual mechanism a "template" needs.
- justfile wrapping the cargo/nix commands for discoverability.
- .envrc (use flake) for direnv auto-activation.
- `just` added to the dev shell so the justfile is usable without a
  separate install.

Left out on purpose: workspace crate split, tracing/serde/tokio-heavy
example code, docker.nix, nightly coverage toolchain, and copier.yaml —
those make it an opinionated async-service starter rather than a blank
Rust template, and copier.yaml in the old repo was dead configuration
(no file actually referenced its Jinja variables).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ALEXkc7pTro7tF1WcUuKb
2026-08-11 00:50:51 +08:00

6.7 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.

Workflow for someone starting a new project from this template: copy/clone the repo, run scripts/rename-project.sh <your_snake_case_name> to replace the rust_template placeholder everywhere, mv the directory itself (the script prints the exact command), confirm with nix flake check (or cargo test), then build whatever you actually came here to build — replace greet in src/lib.rs, extend the CLI in src/main.rs, add real tests/benches as you go.

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.

justfile wraps the commands above (just build, just test, just ci, just nix-check, ...; run just with no args to list them) — available inside nix develop. .envrc (use flake) lets direnv users skip typing nix develop entirely.

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.nix is the single source of truth for the Rust build environment: the fenix toolchain, craneLib, commonArgs (source filtering, strictDeps, native/build inputs), and the cached cargoArtifacts (deps built once via craneLib.buildDepsOnly, then reused by the package build, clippy, doc, and nextest checks — this is what makes nix build incremental instead of recompiling all dependencies on every source change). packages.default and devShells.default both derive from this file's output, which is why they stay close to identical.
  • nix/devshell.nix builds the dev shell from that same craneLib/toolchain via craneLib.devShell, adding dev-only tools (cargo-nextest, cargo-criterion, cargo-audit, cargo-expand, taplo, bacon, mold on Linux) that aren't part of the package build itself.
  • nix/treefmt.nix is a flake-parts module enabling rustfmt/nixfmt/deadnix/statix/taplo under treefmt-nix, wired into both nix fmt and the formatting check in flake.nix.
  • flake.nix ties it together with flake-parts: it imports the treefmt module, evaluates nix/rust.nix per-system, and exposes packages.default, checks, apps.default, and devShells.default from its outputs. It also declares flake.templates.default, so once this repo is published, nix flake init -t <url> scaffolds a new project straight from it.
  • src/lib.rs holds the actual logic (currently just greet); src/main.rs is a thin clap wrapper around it. Keep new functionality in the lib crate, not main.rs, so it stays reachable from tests/ and benches/ (both depend on the rust_template lib crate by name, not on the binary).
  • tests/greet.rs is the example integration test (tests the public lib API from outside the crate); src/lib.rs's #[cfg(test)] mod tests is 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 in tests/.
  • benches/greet.rs is a criterion benchmark against the lib crate; Cargo.toml disables 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.