23.11 Cargo Workspaces

Workspaces allow you to manage multiple related crates within a single top-level structure. All crates in a workspace share a single target/ directory and a single Cargo.lock file.

23.11.1 Use Cases

Workspaces are useful for:

  • Large Projects: Breaking down a complex application or library into smaller, more manageable internal crates.
  • Related Crates: Developing several crates (e.g., a core library, a CLI frontend, a web server) that depend on each other.
  • Monorepos: Managing multiple distinct but potentially related projects in one repository.

23.11.2 Setting Up a Workspace

  1. Create a top-level directory for the workspace.
  2. Inside it, create a Cargo.toml file that defines the workspace members. This file typically doesn’t define a [package] itself, only the [workspace] section.
  3. Place the individual crate directories (each with its own Cargo.toml) inside the workspace directory or list paths to them.
my_workspace/
├── Cargo.toml          # Workspace root manifest
├── member_lib/         # A library crate
│   ├── Cargo.toml
│   └── src/lib.rs
└── member_bin/         # A binary crate using the library
    ├── Cargo.toml
    └── src/main.rs
# Shared target and lock file will appear here after build:
# ├── Cargo.lock
# └── target/

my_workspace/Cargo.toml:

[workspace]
members = [
    "member_lib",
    "member_bin",
    # You can also use globs: "crates/*"
]

# Optional: Define settings shared across the workspace
[workspace.dependencies]
# Define common dependencies once here
# Example:
# serde = { version = "1.0", features = ["derive"] }

# Member crates can then inherit this:
# serde = { workspace = true, features = ["derive"] } # 'features' overrides if needed

# Optional: Configure dependency resolution strategy
# resolver = "2" # Use the version 2 feature resolver (default since Rust 1.51)

my_workspace/member_bin/Cargo.toml:

[package]
name = "member_bin"
version = "0.1.0"
edition = "2021"

[dependencies]
# Reference the library crate within the workspace via path or just name
member_lib = { path = "../member_lib" }
# Or if defined in [workspace.dependencies]:
# serde = { workspace = true }

23.11.3 Working with Workspaces

  • Cargo commands run from the workspace root operate on all members by default (e.g., cargo build, cargo test, cargo check).
  • Use the -p <crate_name> or --package <crate_name> flag to target a specific member crate:
    # Build only member_bin
    cargo build -p member_bin
    
    # Run the binary from member_bin
    cargo run -p member_bin
    
    # Test only member_lib
    cargo test -p member_lib
    
  • Publishing: cargo publish run from the root will attempt to publish all publishable members. Use -p to publish specific members.

23.11.4 Benefits

  • Shared Build Cache: Dependencies are compiled only once for the entire workspace.
  • Consistent Dependency Versions: A single Cargo.lock ensures all crates use the same resolved versions of external dependencies.
  • Easier Inter-Crate Development: Changes in one crate are immediately available to others in the workspace without needing to publish intermediate versions.
  • Atomic Operations: Running tests or checks across the whole project is straightforward.