rgoussu@goussu: ~/library/rust/build-ecosystem
~/library/rust/build-ecosystem cat cargo-in-depth.md

Cargo in depth — workspaces, features, profiles & publishing

# The cargo project model — workspace layout and dependency inheritance, additive features and unification, build profiles, build.rs, crates.io publishing, and lockfile semantics.

Conceptsaved 2026-08-09 #rust#cargo#build#packaging#workspaces

Overview

Beyond the daily command surface, cargo is a project model: how multi-crate repositories are laid out (workspaces), how optional functionality is expressed and resolved (features), how build settings are grouped (profiles), how native dependencies hook in (build.rs), and how code reaches crates.io. These five mechanisms are where real-world Rust projects are actually engineered — and where the sharp edges (feature unification, lockfile drift, build-script abuse) live.

Key points

  • Workspaces: one Cargo.lock and one target/ for many member crates; a virtual manifest (root Cargo.toml with only [workspace]) is the norm for services split into crates/*. [workspace.dependencies] declares versions once and members inherit with dep.workspace = true — the version-catalog pattern, first-party.
  • Features are additive flags: [features] names optional deps and cfg(feature) gates; unification means the build uses the union of features every dependent asks for — so a feature must never change behaviour, only add it. Default features and default-features = false are the slimming levers.
  • Profiles: dev (opt-level 0, incremental), release (opt-level 3), plus test/bench inheriting them. The release recipe worth memorizing: lto = "thin" (or true), codegen-units = 1, panic = "abort" where unwinding is unneeded, strip = true; custom profiles ([profile.profiling]) cover release-with-debug-info — see Profiling & debugging.
  • build.rs runs before compilation: compile C via the cc crate, generate bindings (bindgen) or protobuf stubs (tonic-build), emit cargo::rustc-link-lib=…/cargo::rerun-if-changed=… directives. Powerful and abused — a heavy build.rs is a supply-chain and build-time liability (it runs arbitrary code at build time, which is why cargo-vet/deny audits exist).
  • Publishing: cargo publish uploads an immutable .crate to crates.io — semver discipline enforced socially (and by cargo-semver-checks), cargo yank hides a version from new resolutions without deleting it, owners/teams gate who can publish, rust-version (MSRV) declares the minimum toolchain.
  • Lockfile semantics: Cargo.lock pins the exact resolved graph; cargo update moves within semver ranges. Commit it for binaries and — per current guidance — for libraries too (CI reproducibility); lib consumers ignore it by design. The MSRV-aware resolver (edition 2024 / resolver v3) can pick older compatible versions for older toolchains.
  • Registries beyond crates.io: [registries] config for private ones; git and path dependencies for unpublished code (path deps are local-only — publishing requires a registry version).

Details

Feature unification, the classic trap

If crate A wants serde without derive and crate B wants serde/derive, everyone gets derive — fine, additive. But if a crate misuses a feature to switch behaviour (a strict-mode feature, say), unification silently turns it on for dependents who never asked. Corollaries: features must be additive; mutually-exclusive features are a design bug (use cfg attributes or separate crates); and cargo tree -e features answers "why is this feature on?". --all-features in CI plus cargo hack --feature-powerset on libraries catch the combinations users will hit.

Workspace layout, the house norm

# Cargo.toml (virtual manifest)
[workspace]
members = ["crates/*"]
resolver = "3"

[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }

# crates/api/Cargo.toml
[dependencies]
tokio.workspace = true
core = { path = "../core" }

Binaries and their libraries split (api bin + core lib) so integration tests and benches link the library; shared lints via [workspace.lints].

Related