The Book·Chapter 14·9 min

More about Cargo and crates.io

Release profiles, workspaces, custom subcommands, and the gates an orchestrator standardizes

By now cargo build, cargo test, and cargo run are reflex. This chapter is about everything else Cargo does. Most of it matters for orchestrators because Cargo is where you anchor the rules an agent has to follow.

Release profiles

Cargo ships two profiles by default: dev (the default when you run cargo build) and release (when you pass --release). They differ on optimization, debug info, and a few other knobs.

You override them in Cargo.toml:

Cargo.toml
[profile.dev]
opt-level = 0
debug = true
 
[profile.release]
opt-level = 3
debug = false
lto = "thin"
codegen-units = 1

The knobs worth knowing:

SettingWhat it controlsTrade
opt-level0-3 plus "s", "z"Higher is faster runtime, slower compile
debugDebug info embedded in binaryBigger binary, better stack traces
ltoLink-time optimization ("thin", "fat", false)Slower link, faster runtime
codegen-unitsParallelism during codegenLower is slower compile, faster runtime
stripStrip symbols from binarySmaller binary, no symbol-level debugging

For benchmarking, use cargo build --release. Numbers from a dev build are not real.

Workspaces: shared target, shared deps

A workspace is a directory with one root Cargo.toml and many member crates. One target/ directory, one Cargo.lock, one shared dependency graph.

Cargo.toml
[workspace]
resolver = "2"
members = ["crates/*"]
 
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
thiserror = "2"

In each member crate:

crates/foo/Cargo.toml
[dependencies]
tokio = { workspace = true }
serde = { workspace = true }

workspace.dependencies is the antidote to version drift across crates. One bump in the root, every member follows.

Workspace lints: the policy gate

[workspace.lints] lets the root crate dictate lint levels for every member. This is where you bake "no unwrap in production code" into the build.

Cargo.toml
[workspace.lints.clippy]
unwrap_used = "deny"
expect_used = "deny"
panic = "deny"
todo = "deny"
unimplemented = "deny"
dbg_macro = "deny"

Each member crate opts in:

crates/foo/Cargo.toml
[lints]
workspace = true

Now cargo clippy --workspace fails the build if any member crate uses .unwrap(), .expect("..."), or panic!(). Agents will try. The gate stops them at CI time.

Test modules need an escape hatch. Use #[expect(clippy::unwrap_used)] on the test module so that cargo clippy still flags new unwraps that drift outside tests.

#[cfg(test)]
#[expect(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;
    // tests freely use .unwrap() here
}

Publishing to crates.io

Most readers will not publish a crate. The short version: cargo login <token>, add description, license, repository, and readme fields to Cargo.toml, then cargo publish. Once a version is published it cannot be deleted, only yanked. Use cargo publish --dry-run first.

Installing binaries

Cargo doubles as a binary installer:

cargo install ripgrep
cargo install cargo-nextest --locked
cargo install --git https://github.com/foo/bar

Binaries go in ~/.cargo/bin/, which should be on your PATH. --locked uses the upstream Cargo.lock rather than resolving fresh, which makes installs reproducible. Use it.

Extending Cargo

Any binary on your PATH named cargo-foo becomes the subcommand cargo foo. This is how cargo-watch, cargo-edit, cargo-nextest, and friends work.

Tools worth having installed by default:

ToolWhat it does
cargo-watchRe-runs a command on file changes
cargo-editcargo add, cargo rm, cargo upgrade (modern Cargo ships add and rm natively)
cargo-denyLicense + vuln + duplicate dependency policy
cargo-nextestFaster, more informative test runner
cargo-flamegraphProfile-guided flamegraphs
cargo-bloatWhat is making your binary big
cargo-macheteFind unused dependencies
cargo-auditRustSec vulnerability scan

cargo-deny and cargo-audit belong in CI for any production crate.

The orchestrator's standard gate

A workspace where agents push code should standardize on these CI checks, in this order:

cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
cargo deny check

Every gate is non-negotiable. -D warnings is what makes clippy actually block; without it, warnings are visible but landable. The agent learns very quickly that lazy code does not pass.