Skip to content

Workspaces & Feature Flags

When a TypeScript monorepo grows beyond a single package, you reach for npm (or pnpm/yarn) workspaces. Cargo has a first-class equivalent: a workspace is a single Cargo.toml at the root that lists member crates. All members share a single Cargo.lock and a single target/ build directory.

TypeScript
# package.json (root)
{
"name": "my-monorepo",
"workspaces": [
"packages/api",
"packages/core",
"packages/cli"
]
}
# packages/core/package.json
{
"name": "@my/core",
"version": "1.0.0"
}
Rust
# Cargo.toml (rootthe workspace manifest)
[workspace]
members = [
"crates/api",
"crates/core",
"crates/cli",
]
resolver = "2" # always use resolver version 2
# crates/core/Cargo.toml
[package]
name = "core"
version = "0.1.0"
edition = "2021"
Terminal window
# TypeScript (npm workspaces)
npm run test --workspaces
npm run build --workspace packages/api
# Cargo
cargo build --workspace # build every member
cargo test --workspace # test every member
cargo build -p api # build only the "api" crate
cargo test -p core # test only the "core" crate

Sharing a dependency version across crates

Section titled “Sharing a dependency version across crates”
# Cargo.toml (root workspace manifest)
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
# crates/api/Cargo.toml — inherit without repeating the version
[dependencies]
tokio = { workspace = true }
serde = { workspace = true }

This prevents version drift across workspace members — a common problem in Node monorepos where different packages silently pull different patch versions.

Feature flags are Rust’s answer to optional peer dependencies and conditional require() calls. They let consumers opt into extra functionality without paying the compile cost when they do not need it.

Cargo.toml
[features]
default = ["json"] # features enabled by default
json = ["dep:serde_json"] # "json" feature pulls in serde_json
async = ["dep:tokio"] # "async" feature pulls in tokio
full = ["json", "async"] # convenience: enable everything
[dependencies]
serde_json = { version = "1", optional = true }
tokio = { version = "1", optional = true }
src/lib.rs
#[cfg(feature = "json")]
pub mod json_support {
pub fn parse(s: &str) -> serde_json::Value {
serde_json::from_str(s).unwrap()
}
}
Terminal window
# Build with no default features
cargo build --no-default-features
# Enable specific features
cargo build --features "async,json"
# Enable all features
cargo build --all-features

Workspaces and feature flags require multi-file project layout, so there is no browser Playground for this lesson. Create a workspace locally with cargo new --lib and experiment with the examples above.

In a Cargo workspace, where is the single shared Cargo.lock file located?
How do you build only one specific crate in a workspace?
What is the purpose of Cargo feature flags?