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()
    }
}

🧠 Test Your Knowledge

How do you reference a workspace dependency in a member package?