Rust Workspaces
Managing multiple related packages together
🏗️ What are Workspaces?
Workspaces help organize multiple related Rust packages in a single repository. They share dependencies, build artifacts, and configuration while maintaining separate crates for better code organization and modularity.
# Workspace Cargo.toml
[workspace]
members = [
"web-server",
"database",
"shared-utils"
]
Workspace Benefits
Shared Dependencies
Common dependencies are shared across packages
[workspace.dependencies]
serde = "1.0"
tokio = "1.0"
Faster Builds
Shared build cache and artifacts
cargo build --workspace
cargo test --workspace
Code Organization
Separate concerns into focused packages
my-app/
├── api/
├── database/
└── shared/
Internal Dependencies
Easy references between workspace packages
shared-utils = { path = "../shared" }
🔹 Creating a Workspace
Set up a workspace with multiple packages:
# Create workspace directory
mkdir my-workspace
cd my-workspace
# Create root Cargo.toml
touch Cargo.toml
# Create member packages
cargo new --lib shared-utils
cargo new --bin web-server
cargo new --lib database
🔸 Root Cargo.toml
[workspace]
members = [
"shared-utils",
"web-server",
"database"
]
resolver = "2"
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
anyhow = "1.0"
🔹 Workspace Structure
A typical workspace layout:
my-workspace/
├── Cargo.toml # Workspace configuration
├── Cargo.lock # Shared dependency lock
├── target/ # Shared build directory
├── shared-utils/ # Utility library
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── web-server/ # Web server binary
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
└── database/ # Database library
├── Cargo.toml
└── src/
└── lib.rs
Key points:
- Root Cargo.toml defines workspace members
- Single Cargo.lock for all packages
- Shared target/ directory for builds
- Each member has its own Cargo.toml
🔹 Package Dependencies
Reference workspace members and shared dependencies:
🔸 web-server/Cargo.toml
[package]
name = "web-server"
version = "0.1.0"
edition = "2021"
[dependencies]
# Workspace dependencies
serde = { workspace = true }
tokio = { workspace = true }
# Internal workspace member
shared-utils = { path = "../shared-utils" }
database = { path = "../database" }
# Package-specific dependency
axum = "0.7"
🔸 Using workspace dependencies in code
// web-server/src/main.rs
use shared_utils::config::Config;
use database::models::User;
#[tokio::main]
async fn main() {
let config = Config::load();
let user = User::new("Alice", "[email protected]");
println!("Server starting with config: {:?}", config);
println!("Created user: {:?}", user);
}
🔹 Workspace Commands
Manage the entire workspace with Cargo commands:
# Build all packages
cargo build --workspace
# Run tests for all packages
cargo test --workspace
# Build specific package
cargo build -p web-server
# Run specific binary
cargo run -p web-server
# Check all packages
cargo check --workspace
# Clean workspace
cargo clean
Useful flags:
- --workspace: Apply to all members
- -p package: Target specific package
- --exclude package: Skip specific package
- --all: Alias for --workspace
🔹 Example: Shared Utilities
Create reusable code across workspace packages:
🔸 shared-utils/src/lib.rs
pub mod config {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub database_url: String,
pub port: u16,
}
impl Config {
pub fn load() -> Self {
Config {
database_url: "postgres://localhost/mydb".to_string(),
port: 8080,
}
}
}
}
pub mod utils {
pub fn format_timestamp() -> String {
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
}
}