If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.
14.5.1 Why Cargo Workspaces Are Needed
Suppose we build a binary crate that contains both a library and an application. As the project grows, the library crate may become larger and larger. In that case, it is usually split into multiple packages. For this need, Rust provides Cargo workspaces, also called cargo workspaces.
Cargo workspaces help manage multiple related crates that need to be developed together. In essence, they are a set of packages that share the same Cargo.lock and output files.
14.5.2 Using a Workspace
There are multiple ways to create a workspace.
Here is an example: this workspace contains one binary crate and one library crate:
- The binary crate has a
mainfunction and depends on the library crate. - One library crate provides a function called
add_one.
1. Create the Workspace Directory
First, create a directory for the workspace. I will name it add. Enter the following commands in the terminal:
$ mkdir add
$ cd add
2. Use the Workspace in the Main Project
Next, inside the add directory, create a Cargo.toml file that configures the entire workspace. This file will not have a [package] section. Instead, it starts with a [workspace] section:
[workspace]
members = [
"adder",
]
adder is the name I gave the binary crate, and this list can be extended with more members.
3. Add the Library
$ cargo new adder
Created binary (application) `adder` package
This command creates the adder crate under add/adder.
At this point, the project structure looks like this:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
Note that at this point, we can run cargo build for the add project, and we can also run cargo build for the adder crate under add. However, there will only be one target directory and one Cargo.lock file, both under add, and the build output of the adder crate will also be stored there. Because crates in a workspace are often interdependent, having a separate target directory for each folder would force developers to repeatedly rebuild the other crates in the workspace.
Next, add another crate:
The other crate is called add_one, so update the workspace information:
[workspace]
members = [
"adder",
"add_one",
]
Use cargo new to add the library. Remember to use the --lib flag to declare it as a library crate:
$ cargo new add_one --lib
Created library `add_one` package
Now the project structure looks like this:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
4. Write the Code
In add_one/src/lib.rs, add an add_one function:
pub fn add_one(x: i32) -> i32 {
x + 1
}
Now we can make the adder package and our binary depend on add_one. First, add the path dependency add_one to adder/Cargo.toml, because Cargo does not assume that crates in a workspace depend on each other, so we need to make the dependency explicit. Write this in adder/Cargo.toml:
[dependencies]
add_one = { path = "../add_one" }
Next, let’s use the add_one function from the add_one crate. Open adder/src/main.rs, add use at the top to bring add_one into scope, and then modify main to call the add_one function.
use add_one;
fn main() {
let num = 10;
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
5. Build
Run cargo build for the add project:
$ cargo build
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.68s
No errors occurred; it runs normally.
6. Testing
We can also run tests for a specific package in the workspace from the top-level directory by using the -p flag and specifying the package name. For example, to test only the add_one function:
$ cargo test -p add_one
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/add_one-b3235fea9a156f74)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests add_one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
If you publish the crates in a workspace to crates.io, each crate in the workspace must be published separately. As with cargo test, we can use -p to target a specific package in the workspace and specify the package name we want to publish.