Workspaces
A Cargo workspace is a set of crates built together with one shared Cargo.lock, unified dependency versions, and single-command test/build workflows.
Recipe
# Cargo.toml (workspace root)
[workspace]
members = ["crates/api", "crates/cli", "crates/domain"]
resolver = "2"
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }# crates/api/Cargo.toml
[package]
name = "api"
version = "0.1.0"
edition = "2024"
[dependencies]
domain = { path = "../domain" }
serde = { workspace = true }
tokio = { workspace = true }When to reach for this: Multiple related crates (API, CLI, shared domain) that release and test together.
Working Example
my_workspace/
├── Cargo.toml
├── Cargo.lock
├── crates/
│ ├── domain/
│ ├── api/
│ └── cli/
└── .cargo/config.toml
cargo build --workspace
cargo test -p domain
cargo run -p cli -- --helpWhat this demonstrates:
- One
Cargo.lockat the root pins versions for all members - Path dependencies connect internal crates without publishing
-pselects a single member packageresolver = "2"is the modern feature resolver (default for edition 2021+)
Deep Dive
Workspace Layout Patterns
| Pattern | Layout |
|---|---|
| Flat members | api/, cli/ at repo root |
crates/ dir | crates/api, crates/domain |
| Nested | Only when a member is not the root |
Shared Metadata
[workspace.package]
edition = "2024"
rust-version = "1.97"
license = "MIT OR Apache-2.0"
[workspace.dependencies]
axum = "0.8"Members inherit with:
[package]
name = "api"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = trueVirtual Workspaces
A root Cargo.toml with [workspace] only (no [package]) is a virtual workspace. Common for monorepos that ship multiple binaries and libraries.
Gotchas
- Publishing path deps -
cargo publishon a member must replacepathwith version deps. Fix: usecargo publish --dry-runand crates.io metadata checks. - Feature unification surprises - enabling a feature in one member affects shared deps workspace-wide. Fix: isolate conflicting backends in separate crates.
- Circular path dependencies - A depends on B depends on A fails to resolve. Fix: extract shared types to a third
corecrate. - Duplicate package names - two members cannot share a name. Fix: rename or merge crates.
- CI only testing root - members break silently. Fix:
cargo test --workspace --all-targets.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Single crate + modules | App fits in one binary | Independent publish/version cycles needed |
| Multiple repos | Teams own release cadence | Shared types change daily |
| Bazel / Nx orchestration | Polyglot monorepo | Pure Rust teams with Cargo-native workflows |
FAQs
How many crates belong in one workspace?
Enough to express boundaries (domain, adapters, binaries), not so many that every one-line change rebuilds the world. Split when compile graphs hurt iteration.
What is workspace.default-members?
Lists crates cargo build runs when invoked at the root without -p. Useful when the root is virtual.
Can workspace members have different editions?
Yes per [package] edition, but aligning on 2024 reduces cognitive load.
How do I exclude a folder from the workspace?
Use exclude = ["tools/experimental"] in [workspace] or simply omit it from members.
Where does Cargo.lock live?
At the workspace root. Members do not get their own lockfiles.
How do internal crates version?
Path deps ignore version for resolution; still bump semver on published internal crates if external consumers exist.
Can I run clippy on the whole workspace?
cargo clippy --workspace --all-targets -- -D warnings is the standard CI invocation.
What is resolver = 2?
Feature resolver version 2 avoids activating features on targets that are not built (e.g. dev-deps on unused platforms).
How do I share a build.rs script?
Extract logic to a small internal crate or copy minimal scripts; Cargo does not share build.rs across members automatically.
Workspace vs modules inside one crate?
Modules are free but compile as one unit. Workspace crates enforce API boundaries and parallel compilation.
Related
- Cargo Basics - commands at workspace root
- Workspace & Crate Boundaries - design guidance
- Dependency Management - lockfile updates
- Cargo.toml Explained - workspace tables
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+.