My Nix configuration. Enter at your own risk.
0

Configure Feed

Select the types of activity you want to include in your feed.

rust: Add skill for Cargo workspace hygiene

Add skill covering Rust/Cargo-specific gotchas:
- Build cache explosion (tests/*.rs, build.rs, stale artifacts)
- Feature flag transit through workspace deps
- Compile-time metadata via env vars vs build.rs
- Decision/rendering separation (typed outcomes in core, formatting in CLI)

author
Isaac Corbrey
date (Jun 1, 2026, 10:30 AM -0400) commit 862e11fb parent 615d3b92 change-id yzmpkmry
+140
+140
modules/home/rust/skill.md
··· 1 + --- 2 + name: rust 3 + description: Load this skill for non-trivial work in a Cargo workspace. Covers what is genuinely peculiar to Cargo as an agent encounters it — keeping the build cache from exploding into tens of gigabytes, the workspace-deps feature-flag transit gotcha, and the architectural rule for keeping CLI-bearing crates testable. The general engineering-discipline rules (oracles, testing domains, evidence) live in `rigor`; the commit-shaping rules live in `jujutsu`. 4 + --- 5 + 6 + # Routinely Rigorous Rust for Robots 7 + 8 + Cargo's defaults make some choices that are wrong-by-default for any non-trivial 9 + workspace, and a few of those choices are easy for an agent to walk into 10 + without noticing. This skill covers the Rust-specific hygiene; the more general 11 + engineering-discipline rules (oracle replication, testing-domain classification, 12 + evidence discipline) are in `rigor`, and the commit-shaping rules are in 13 + `jujutsu`. 14 + 15 + Core themes: 16 + 17 + - **Cargo build cache grows without bound.** Every `tests/*.rs` becomes a 18 + separate binary; `build.rs` invalidates incrementally; old artifacts never 19 + garbage-collect. Six rules disarm it. 20 + - **`--no-default-features` doesn't transit workspace deps.** Default-on 21 + features silently re-enable through dependents unless `default-features = 22 + false` at workspace root. 23 + - **Compile-time metadata via env vars, not `build.rs`.** Use `option_env!` + 24 + shell hook. 25 + - **Decisions in core, rendering in CLI.** Typed outcome enums in core; CLI 26 + formats them. Keeps CLI testable. 27 + 28 + You already know `cargo`, `clippy`, `rustfmt`, `nextest`, `proptest`, 29 + `thiserror`. This document is about Cargo-specific *traps* and the small set of 30 + architectural rules that make a Cargo workspace cheap to iterate on as it grows. 31 + 32 + ## Cargo build caches are stupid 33 + 34 + Cargo's defaults produce tens-of-gigabyte `target/` directories: 35 + 36 + - **Every `tests/*.rs` is a separate binary** statically linking 37 + crate-under-test plus dev deps. Ten test files × four crates = forty 38 + multi-hundred-MB binaries. 39 + - **Every `build.rs` runs on invalidation** without garbage collection. Cascades 40 + re-compiles. 41 + - **Never deletes stale artifacts.** Old rlib hashes accumulate indefinitely. 42 + - **`incremental/` grows without bound.** Pure waste in CI fresh-build 43 + environments. 44 + 45 + Fixes (apply at bring-up): 46 + 47 + 1. **One integration-test binary, workspace-wide.** Add a dedicated 48 + crate (e.g. `crates/integration-tests/`) whose `tests/integration.rs` 49 + is the only top-level test file across the entire workspace. 50 + Sub-suites become `mod` declarations in that file with their bodies in 51 + `tests/integration/<topic>.rs`. See [matklad's "Delete Cargo Integration 52 + Tests"] for the rationale. 53 + 2. **Zero `build.rs`.** When you need compile-time metadata (a build 54 + SHA, an embedded asset), reach for `option_env!("MY_BUILD_REV")` plus 55 + `const_format::concatcp!` and have the build environment (a flake's 56 + `shellHook`, a CI step) set the variable. Reach for `include_bytes!` for 57 + embedded files. The cases that genuinely require a build script are rare and 58 + should be a deliberate design decision, not a default. 59 + 3. **No `examples/`, no doctests.** Both compile as separate binaries. Set 60 + `doctest = false` in each crate's `[lib]` section; do not add an `examples/` 61 + directory. A snippet worth testing is worth promoting to a real test in the 62 + integration crate. 63 + 4. **Workspace-deduplicated dependencies.** Declare every external dep exactly 64 + once in the root `[workspace.dependencies]` and reference it from member 65 + crates with `workspace = true`. This prevents linking multiple versions of 66 + the same crate. 67 + 5. **`cargo-sweep` on a cadence.** Cargo will never delete stale artefacts 68 + itself. `cargo sweep --time 14` periodically drops anything untouched for 69 + two weeks. 70 + 6. **`CARGO_INCREMENTAL=0` in CI.** Every CI lane should export this; the 71 + incremental cache is pure waste in fresh-build environments. Keep it on 72 + locally for iteration speed. 73 + 74 + ## `default-features = false` does not transit workspace deps 75 + 76 + Cargo footgun: `--no-default-features` at workspace root **does not** override 77 + `default-features = true` on intra-workspace deps. If one crate defaults a 78 + system-library feature on and a sibling depends without `default-features 79 + = false`, `cargo build --workspace --no-default-features` *still* pulls the 80 + feature transitively — build fails when library absent. 81 + 82 + The fix is to set `default-features = false` on every internal-crate declaration 83 + in `[workspace.dependencies]`: 84 + 85 + ```toml 86 + [workspace.dependencies] 87 + my-core = { path = "crates/core", default-features = false } 88 + my-daemon = { path = "crates/daemon", default-features = false } 89 + my-cli = { path = "crates/cli", default-features = false } 90 + ``` 91 + 92 + Crates needing defaults can flip `default-features = true` in their own 93 + `[dependencies]`. Makes dependence explicit. Default is "off, except where 94 + deliberate." 95 + 96 + **Verify:** After touching `[features]` or internal deps, build with 97 + `--workspace --no-default-features --all-targets` locally before committing. 98 + Feature bugs usually caught by CI lacking system libraries; catch on branch, not 99 + in review. 100 + 101 + ## Decisions in core, rendering in the CLI 102 + 103 + The architectural rule (referenced briefly in `rigor` § *Note open vs. 104 + closed testing domains*) is, for Rust: **anything that can be decided without 105 + printing belongs in a core/library crate, returns a typed outcome enum, and 106 + prints nothing; the CLI crate is a thin formatter over those outcomes.** The 107 + compiler-checked test of whether you got the boundary right is whether the 108 + core function has any `eprintln!`/`println!` in it — it should have none — and 109 + whether the CLI's `match` arms pick between *renderings* of an outcome rather 110 + than between *operations*. 111 + 112 + Enforcement: 113 + 114 + - **`match` arm with `eprintln!` is wrong.** Lift decision into core return 115 + type as enum variant. CLI renders it. "Ambiguous" case is `Ambiguous { chosen, 116 + alternatives }`, not a print. 117 + - **Errors carry structure.** Variant-rich enums (`StoreError`, `CliError`) let 118 + UIs map failures to recovery. "User did wrong with remediation" is its own 119 + variant, not `Config(String)`. 120 + - **No interactive prompts in core.** Break under piping, add modal state, 121 + non-CLI UIs can't reuse. Emit `Ambiguous`-style outcome; let UI surface choice. 122 + - **Progress as structured events.** `ProgressEvent` enum streamed to UI. Keeps 123 + core pure, renderers independent. 124 + 125 + Compiler test: core compiles without `std::io` or tests produce no stray bytes 126 + = no decisions hiding in renderers. Removing `eprintln!` requires new outcome 127 + variant? Variant was the missing decision. 128 + 129 + ## Quick reference 130 + 131 + | Situation | What to actually do | 132 + | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | 133 + | Embedded compile-time metadata | `option_env!(...)` plus `const_format::concatcp!`; do *not* add a `build.rs` | 134 + | New workspace, first `[workspace.dependencies]` | `default-features = false` on every internal-crate entry; build with `--no-default-features --all-targets` to verify | 135 + | New cross-crate integration test | Add it as a `mod` under `crates/integration-tests/tests/integration/`; do *not* add a top-level `tests/*.rs` anywhere else | 136 + | Decision-bearing CLI command | Pure resolver in core returning a typed outcome enum; CLI subcommand is a thin formatter | 137 + | `match` arm with `eprintln!` inside it | Extract the decision into a typed outcome enum in core; CLI dispatches on the variant | 138 + | Generic `Config(String)` error variant | Replace with a named structural variant; `String` is squashing a real outcome | 139 + | `target/` is dozens of GB | `cargo sweep --time 14`; verify there's only one top-level `tests/*.rs` in the workspace; check no `build.rs` crept in | 140 + | Touched any `[features]` or internal dep entry | Build `--workspace --no-default-features --all-targets` locally before committing |