Workspaces
Cargo workspaces group multiple packages with shared Cargo.lock and optional [workspace.dependencies].
Recipe
# Cargo.toml (root)
[workspace]
members = ["crates/api", "crates/cli"]
resolver = "2"
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }When to reach for this: Monorepos with library + binary + internal crates sharing versions.
Working Example
# crates/api/Cargo.toml
[package]
name = "api"
version = "0.1.0"
edition = "2024"
[dependencies]
serde = { workspace = true }cargo test --workspace
cargo build -p cliWhat this demonstrates:
- Workspace dependency inheritance
-pselects package- Single lockfile for reproducible builds
Deep Dive
default-members for bare cargo build. exclude directories. Virtual workspace (no root package) common.
Gotchas
- Duplicate versions without workspace deps - Bloat. Fix: Centralize in workspace.dependencies.
- Publishing subset - Only publish leaf crates, not virtual manifest alone.
- path deps vs workspace -
path = "../core"in members. - feature unification - Resolver 2 unifies features across workspace.
- CI without --workspace - Misses member failures. Fix:
cargo test --workspace.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Single crate modules | Small project | Independent versioning needed |
| Separate repos | Independent release | Shared atomic changes |
| cargo workspaces + git deps | Cross repo | Same repo simpler |
FAQs
virtual workspace?
Root has only [workspace] no [package].default-members?
cargo run default target.shared metadata?
workspace.package table.rust-toolchain?
One file at root.clippy workspace?
cargo clippy --workspace.patch section?
Override deps workspace-wide.exclude?
Skip non-member folders.rename member?
Update members path.depend member?
api = { path = "../api" }.docs?
cargo doc --workspace.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.