Compile-Time and Developer Tools ๐ก
What you'll learn:
- Compilation caching with
sccachefor local and CI builds- Faster linking with
mold(3-10ร faster than the default linker)cargo-nextest: a faster, more informative test runner- Developer visibility tools:
cargo-expand,cargo-geiger,cargo-watch- Workspace lints, MSRV policy, and documentation-as-CI
Cross-references: Release Profiles โ LTO and binary size optimization ยท CI/CD Pipeline โ these tools integrate into your pipeline ยท Dependencies โ fewer deps = faster compiles
Compile-Time Optimization: sccache, mold, cargo-nextest
Long compile times are the #1 developer pain point in Rust. These tools collectively can cut iteration time by 50-80%:
sccache โ Shared compilation cache:
# Install
cargo install sccache
# Configure as the Rust wrapper
export RUSTC_WRAPPER=sccache
# Or set permanently in .cargo/config.toml:
# [build]
# rustc-wrapper = "sccache"
# First build: normal speed (populates cache)
cargo build --release # 3 minutes
# Clean + rebuild: cache hits for unchanged crates
cargo clean && cargo build --release # 45 seconds
# Check cache statistics
sccache --show-stats
# Compile requests 1,234
# Cache hits 987 (80%)
# Cache misses 247
sccache supports shared caches (S3, GCS, Azure Blob) for team-wide and CI
cache sharing.
mold โ A faster linker:
Linking is often the slowest phase. mold is 3-5ร faster than lld and
10-20ร faster than the default GNU ld:
# Install
sudo apt install mold # Ubuntu 22.04+
# Note: mold is for ELF targets (Linux). macOS uses Mach-O, not ELF.
# The macOS linker (ld64) is already quite fast; if you need faster:
# brew install sold # sold = mold for Mach-O (experimental, less mature)
# In practice, macOS link times are rarely a bottleneck.
# Use mold for linking
# .cargo/config.toml
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
# Verify mold is being used
cargo build -v 2>&1 | grep mold
cargo-nextest โ A faster test runner:
# Install
cargo install cargo-nextest
# Run tests (parallel by default, per-test timeout, retry)
cargo nextest run
# Key advantages over cargo test:
# - Each test runs in its own process โ better isolation
# - Parallel execution with smart scheduling
# - Per-test timeouts (no more hanging CI)
# - JUnit XML output for CI
# - Retry failed tests
# Configuration
cargo nextest run --retries 2 --fail-fast
# Archive test binaries (useful for CI: build once, test on multiple machines)
cargo nextest archive --archive-file tests.tar.zst
cargo nextest run --archive-file tests.tar.zst
# .config/nextest.toml
[profile.default]
retries = 0
slow-timeout = { period = "60s", terminate-after = 3 }
fail-fast = true
[profile.ci]
retries = 2
fail-fast = false
junit = { path = "test-results.xml" }
Combined dev configuration:
# .cargo/config.toml โ optimize the development inner loop
[build]
rustc-wrapper = "sccache" # Cache compilation artifacts
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"] # Faster linking
# Dev profile: optimize deps but not your code
# (put in Cargo.toml)
# [profile.dev.package."*"]
# opt-level = 2
cargo-expand and cargo-geiger โ Visibility Tools
cargo-expand โ see what macros generate:
cargo install cargo-expand
# Expand all macros in a specific module
cargo expand --lib accel_diag::vendor
# Expand a specific derive
# Given: #[derive(Debug, Serialize, Deserialize)]
# cargo expand shows the generated impl blocks
cargo expand --lib --tests
Invaluable for debugging #[derive] macro output, macro_rules! expansions,
and understanding what serde generates for your types.
cargo-geiger โ count unsafe usage across your dependency tree:
cargo install cargo-geiger
cargo geiger
# Output:
# Metric output format: x/y
# x = unsafe code used by the build
# y = total unsafe code found in the crate
#
# Functions Expressions Impls Traits Methods
# 0/0 0/0 0/0 0/0 0/0 โ
my_crate
# 0/5 0/23 0/2 0/0 0/3 โ
serde
# 3/3 14/14 0/0 0/0 2/2 โ libc
# 15/15 142/142 4/4 0/0 12/12 โข๏ธ ring
# The symbols:
# โ
= no unsafe used
# โ = some unsafe used
# โข๏ธ = heavily unsafe
For the project's zero-unsafe policy, cargo geiger verifies that no
dependency introduces unsafe code into the call graph that your code actually
exercises.
Workspace Lints โ [workspace.lints]
Since Rust 1.74, you can configure Clippy and compiler lints centrally in
Cargo.toml โ no more #![deny(...)] at the top of every crate:
# Root Cargo.toml โ lint configuration for all crates
[workspace.lints.clippy]
unwrap_used = "warn" # Prefer ? or expect("reason")
dbg_macro = "deny" # No dbg!() in committed code
todo = "warn" # Track incomplete implementations
large_enum_variant = "warn" # Catch accidental size bloat
[workspace.lints.rust]
unsafe_code = "deny" # Enforce zero-unsafe policy
missing_docs = "warn" # Encourage documentation
# Each crate's Cargo.toml โ opt into workspace lints
[lints]
workspace = true
This replaces scattered #![deny(clippy::unwrap_used)] attributes and ensures
consistent policy across the entire workspace.
Auto-fixing Clippy warnings:
# Let Clippy automatically fix machine-applicable suggestions
cargo clippy --fix --workspace --all-targets --allow-dirty
# Fix and also apply suggestions that may change behavior (review carefully!)
cargo clippy --fix --workspace --all-targets --allow-dirty -- -W clippy::pedantic
Tip: Run
cargo clippy --fixbefore committing. It handles trivial issues (unused imports, redundant clones, type simplifications) that are tedious to fix by hand.
MSRV Policy and rust-version
Minimum Supported Rust Version (MSRV) ensures your crate compiles on older toolchains. This matters when deploying to systems with frozen Rust versions.
# Cargo.toml
[package]
name = "diag_tool"
version = "0.1.0"
rust-version = "1.75" # Minimum Rust version required
# Verify MSRV compliance
cargo +1.75.0 check --workspace
# Automated MSRV discovery
cargo install cargo-msrv
cargo msrv find
# Output: Minimum Supported Rust Version is 1.75.0
# Verify in CI
cargo msrv verify
MSRV in CI:
jobs:
msrv:
name: Check MSRV
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.75.0" # Match rust-version in Cargo.toml
- run: cargo check --workspace
MSRV strategy:
- Binary applications (like a large project): Use latest stable. No MSRV needed.
- Library crates (published to crates.io): Set MSRV to oldest Rust version
that supports all features you use. Commonly
N-2(two versions behind current). - Enterprise deployments: Set MSRV to match the oldest Rust version installed on your fleet.
Application: Production Binary Profile
The project already has an excellent release profile:
# Current workspace Cargo.toml
[profile.release]
lto = true # โ
Full cross-crate optimization
codegen-units = 1 # โ
Maximum optimization
panic = "abort" # โ
No unwinding overhead
strip = true # โ
Remove symbols for deployment
[profile.dev]
opt-level = 0 # โ
Fast compilation
debug = true # โ
Full debug info
Recommended additions:
# Optimize dependencies in dev mode (faster test execution)
[profile.dev.package."*"]
opt-level = 2
# Test profile: some optimization to prevent timeout in slow tests
[profile.test]
opt-level = 1
# Keep overflow checks in release (safety)
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
strip = true
overflow-checks = true # โ add this: catch integer overflows
debug = "line-tables-only" # โ add this: backtraces without full DWARF
Recommended developer tooling:
# .cargo/config.toml (proposed)
[build]
rustc-wrapper = "sccache" # 80%+ cache hit after first build
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"] # 3-5ร faster linking
Expected impact on the project:
| Metric | Current | With Additions |
|---|---|---|
| Release binary | ~10 MB (stripped, LTO) | Same |
| Dev build time | ~45s | ~25s (sccache + mold) |
| Rebuild (1 file change) | ~15s | ~5s (sccache + mold) |
| Test execution | cargo test | cargo nextest โ 2ร faster |
| Dep vulnerability scanning | None | cargo audit in CI |
| License compliance | Manual | cargo deny automated |
| Unused dependency detection | Manual | cargo udeps in CI |
cargo-watch โ Auto-Rebuild on File Changes
cargo-watch re-runs a command
every time a source file changes โ essential for tight feedback loops:
# Install
cargo install cargo-watch
# Re-check on every save (instant feedback)
cargo watch -x check
# Run clippy + tests on change
cargo watch -x 'clippy --workspace --all-targets' -x 'test --workspace --lib'
# Watch only specific crates (faster for large workspaces)
cargo watch -w accel_diag/src -x 'test -p accel_diag'
# Clear screen between runs
cargo watch -c -x check
Tip: Combine with
mold+sccachefrom above for sub-second re-check times on incremental changes.
cargo doc and Workspace Documentation
For a large workspace, generated documentation is essential for
discoverability. cargo doc uses rustdoc to produce HTML docs from
doc-comments and type signatures:
# Generate docs for all workspace crates (opens in browser)
cargo doc --workspace --no-deps --open
# Include private items (useful during development)
cargo doc --workspace --no-deps --document-private-items
# Check doc-links without generating HTML (fast CI check)
cargo doc --workspace --no-deps 2>&1 | grep -E 'warning|error'
Intra-doc links โ link between types across crates without URLs:
/// Runs GPU diagnostics using [`GpuConfig`] settings.
///
/// See [`crate::accel_diag::run_diagnostics`] for the implementation.
/// Returns [`DiagResult`] which can be serialized to the
/// [`DerReport`](crate::core_lib::DerReport) format.
pub fn run_accel_diag(config: &GpuConfig) -> DiagResult {
// ...
}
Show platform-specific APIs in docs:
// Cargo.toml: [package.metadata.docs.rs]
// all-features = true
// rustdoc-args = ["--cfg", "docsrs"]
/// Windows-only: read battery status via Win32 API.
///
/// Only available on `cfg(windows)` builds.
#[cfg(windows)]
#[doc(cfg(windows))] // Shows "Available on Windows only" badge in docs
pub fn get_battery_status() -> Option<u8> {
// ...
}
CI documentation check:
# Add to CI workflow
- name: Check documentation
run: RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
# Treats broken intra-doc links as errors
For the project: With many crates,
cargo doc --workspaceis the best way for new team members to discover the API surface. AddRUSTDOCFLAGS="-D warnings"to CI to catch broken doc-links before merge.
Compile-Time Decision Tree
flowchart TD
START["Compile too slow?"] --> WHERE{"Where's the time?"}
WHERE -->|"Recompiling\nunchanged crates"| SCCACHE["sccache\nShared compilation cache"]
WHERE -->|"Linking phase"| MOLD["mold linker\n3-10ร faster linking"]
WHERE -->|"Running tests"| NEXTEST["cargo-nextest\nParallel test runner"]
WHERE -->|"Everything"| COMBO["All of the above +\ncargo-udeps to trim deps"]
SCCACHE --> CI_CACHE{"CI or local?"}
CI_CACHE -->|"CI"| S3["S3/GCS shared cache"]
CI_CACHE -->|"Local"| LOCAL["Local disk cache\nauto-configured"]
style SCCACHE fill:#91e5a3,color:#000
style MOLD fill:#e3f2fd,color:#000
style NEXTEST fill:#ffd43b,color:#000
style COMBO fill:#b39ddb,color:#000
๐๏ธ Exercises
๐ข Exercise 1: Set Up sccache + mold
Install sccache and mold, configure them in .cargo/config.toml, then measure the compile time improvement on a clean rebuild.
# Install
cargo install sccache
sudo apt install mold # Ubuntu 22.04+
# Configure .cargo/config.toml:
cat > .cargo/config.toml << 'EOF'
[build]
rustc-wrapper = "sccache"
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
EOF
# First build (populates cache)
time cargo build --release # e.g., 180s
# Clean + rebuild (cache hits)
cargo clean
time cargo build --release # e.g., 45s
sccache --show-stats
# Cache hits should be 60-80%+
๐ก Exercise 2: Switch to cargo-nextest
Install cargo-nextest and run your test suite. Compare wall-clock time with cargo test. What's the speedup?
cargo install cargo-nextest
# Standard test runner
time cargo test --workspace 2>&1 | tail -5
# nextest (parallel per-test-binary execution)
time cargo nextest run --workspace 2>&1 | tail -5
# Typical speedup: 2-5ร for large workspaces
# nextest also provides:
# - Per-test timing
# - Retries for flaky tests
# - JUnit XML output for CI
cargo nextest run --workspace --retries 2
Key Takeaways
sccachewith S3/GCS backend shares compilation cache across team and CImoldis the fastest ELF linker โ link times drop from seconds to millisecondscargo-nextestruns tests in parallel per-binary with better output and retry supportcargo-geigercountsunsafeusage โ run it before accepting new dependencies[workspace.lints]centralizes Clippy and rustc lint configuration across a multi-crate workspace