Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
73: Managing Crates and Workspaces
Think of a large professional kitchen. You don't just have one giant table where everyone is chopping, frying, and plating all at once—that would be absolute chaos. Instead, you have stations. There's a prep station, a grill station, and a pastry station. Each station has its own specific tools and focus, but they all operate within the same kitchen, share the same pantry (dependencies), and are all working toward the same goal: getting the meal out to the customer.
In Rust, a Workspace is that kitchen. The individual Crates are the stations. When your project grows beyond a single binary or a single library, trying to cram everything into one crate makes your compile times crawl and your module tree a nightmare to navigate. By splitting your project into a workspace, you can isolate your logic into smaller, manageable pieces that still feel like a single project.
Defining the Kitchen Perimeter
To start a workspace, you don't use a typical cargo new command for the root. Instead, you create a directory and place a Cargo.toml file in it that tells Rust, "Hey, I'm not a crate myself; I'm a manager for other crates."
[workspace]
members = [
"core_logic",
"api_server",
"cli_tool",
]
resolver = "2"
I usually recommend the resolver = "2" setting because it handles feature unification much more intelligently than the old version. Now, the root directory doesn't have a src folder. It just sits there, orchestrating the members listed in that array.
Connecting the Stations
Let's say we're building a payment processing system. You'd probably have a core_logic crate that handles the actual math and validation, and a cli_tool that lets admins trigger refunds. The cli_tool needs to use the functions inside core_logic.
You don't publish core_logic to crates.io just to use it locally. Instead, you use a path dependency in the cli_tool/Cargo.toml:
[dependencies]
core_logic = { path = "../core_logic" }
This is where the magic happens. When you run cargo build from the root of the workspace, Cargo is smart enough to figure out the dependency graph. It builds core_logic first, then feeds it into the cli_tool. If you only change a line of code in the CLI, Cargo won't bother re-compiling the core logic. This is a huge win for your productivity as the codebase grows.
The Single Source of Truth for Dependencies
One thing that often trips people up is where the Cargo.lock file lives. In a workspace, there is only one Cargo.lock file, and it lives at the root.
I love this because it ensures that if both your api_server and your cli_tool use the serde crate, they are using the exact same version. You'll never run into a bizarre bug where your library expects one version of a dependency but your binary is providing another. It keeps the entire "kitchen" in sync.
Managing Shared Dependencies
If you find yourself adding the same five crates to every single member of your workspace, you can define them in the root Cargo.toml using [workspace.dependencies]. This is like putting a staple ingredient in the main pantry rather than buying a separate bag for every station.
# Root Cargo.toml
[workspace.dependencies]
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
Then, in your member crates, you just reference them like this:
# cli_tool/Cargo.toml
[dependencies]
tokio = { workspace = true }
serde = { workspace = true }
This makes upgrading a dependency a breeze. Change the version once in the root, and every crate in your workspace is updated instantly. It's a small detail, but it saves you from a lot of tedious manual editing when your project hits 10+ crates.
📋 Practical Task
Exercise: Engineering a Multi-Crate Payment Processor Engine
Your goal is to restructure a theoretical payment system into a proper Rust workspace to improve modularity and compile times.
- Create a new directory named
payment_system. - Initialize a workspace by creating a
Cargo.tomlfile at the root. Define a workspace with two members:payment_coreandpayment_cli. Useresolver = "2". - Inside the
payment_systemfolder, create the two crates:cargo new payment_core --libcargo new payment_cli
- In
payment_core/src/lib.rs, implement a simple functionpub fn process_payment(amount: f64) -> Stringthat returns a string saying "Processed payment of $[amount]". - In the root
Cargo.toml, addserde = "1.0"to a[workspace.dependencies]section. - Configure
payment_cli/Cargo.tomlto depend on:payment_core(via a relative path).serde(via the workspace inheritance).
- In
payment_cli/src/main.rs, call theprocess_paymentfunction from the core library and print the result to the console. - Run
cargo buildfrom the root directory to ensure the entire workspace compiles successfully.
There are no comments for now.