[Rsut Guide] 14.5. Cargo Workspace

rust dev.to

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 main function 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
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

4. Write the Code

In add_one/src/lib.rs, add an add_one function:

pub fn add_one(x: i32) -> i32 {
    x + 1
}
Enter fullscreen mode Exit fullscreen mode

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" }
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Source: dev.to

arrow_back Back to Tutorials