Cargo Workspaces - The Rust Programming Language

## Cargo Workspaces

In Chapter 12, we built a package that included both a binary crate and a library crate. But what if the library crate continues to get bigger and we want to split our package up further into multiple library crates? As packages grow, separating out major components can be quite useful. In this situation, Cargo has a feature called workspaces that can help us manage multiple related packages that are developed in tandem.

A workspace is a set of packages that will all share the same Cargo.lock and output directory. Let's make a project using a workspace where the code will be trivial so that we can concentrate on the structure of a workspace. We'll have a binary that uses two libraries: one that will provide an `add_one` method and a second that will provide an `add_two` method. Let's start by creating a new crate for the binary:

    $ cargo new --bin adder
         Created binary (application) `adder` project
    $ cd adder

We need to modify the binary package's Cargo.toml to tell Cargo the `adder` package is a workspace. Add this at the bottom of the file:

    [workspace]

Like many Cargo features, workspaces support convention over configuration: we don't need to say anything more than this as long as we follow the convention. The convention is that any crates that we depend on as sub-directories will be part of the workspace. Let's add a path dependency to the `adder` crate by changing the `[dependencies]` section of Cargo.toml to look like this:

    [dependencies]
    add-one = { path = "add-one" }

If we add dependencies that don't have a `path` specified, those will be normal dependencies that aren't in this workspace.

Next, generate the `add-one` crate within the `adder` directory:

    $ cargo new add-one
         Created library `add-one` project

Your `adder` directory should now have these directories and files:

    ├── Cargo.toml
    ├── add-one
    │   ├── Cargo.toml
    │   └── src
    │       └── lib.rs
    └── src
        └── main.rs

In add-one/src/lib.rs, let's add an implementation of an `add_one` function:

Filename: add-one/src/lib.rs

    # #![allow(unused_variables)]
    #fn main() {
    pub fn add_one(x: i32) -> i32 {
        x + 1
    }

    #}

Open up src/main.rs for `adder` and add an `extern crate` line to bring the new `add-one` library crate into scope, and change the `main` function to use the `add_one` function:

    extern crate add_one;

    fn main() {
        let num = 10;
        println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
    }

Let's build it!

    $ cargo build
       Compiling add-one v0.1.0 (file:///projects/adder/add-one)
       Compiling adder v0.1.0 (file:///projects/adder)
        Finished dev [unoptimized + debuginfo] target(s) in 0.68 secs

Note that running `cargo build` in the adder directory built both that crate and the `add-one` crate in adder/add-one, but created only one Cargo.lock and one target directory, both in the adder directory. See if you can add an `add-two` crate in the same way.

Let's now say that we'd like to use the `rand` crate in our `add-one` crate. As usual, we'll add it to the `[dependencies]` section in the `Cargo.toml` for that crate:

Filename: add-one/Cargo.toml

    [dependencies]

    rand = "0.3.14"

And if we add `extern crate rand;` to add-one/src/lib.rs then run `cargo build`, it will succeed:

    $ cargo build
        Updating registry `https://github.com/rust-lang/crates.io-index`
     Downloading rand v0.3.14
       ...snip...
       Compiling rand v0.3.14
       Compiling add-one v0.1.0 (file:///projects/adder/add-one)
       Compiling adder v0.1.0 (file:///projects/adder)
        Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs

The top level Cargo.lock now reflects the fact that `add-one` depends on `rand`. However, even though `rand` is used somewhere in the workspace, we can't use it in other crates in the workspace unless we add `rand` to their Cargo.toml as well. If we add `extern crate rand;` to src/main.rs for the top level `adder` crate, for example, we'll get an error:

    $ cargo build
       Compiling adder v0.1.0 (file:///projects/adder)
    error[E0463]: can't find crate for `rand`
     --> src/main.rs:1:1
      |
    1 | extern crate rand;
      | ^^^^^^^^^^^^^^^^^^^ can't find crate

To fix this, edit Cargo.toml for the top level and indicate that `rand` is a dependency for the `adder` crate.

For another enhancement, let's add a test of the `add_one::add_one` function within that crate:

Filename: add-one/src/lib.rs

    # #![allow(unused_variables)]
    #fn main() {
    pub fn add_one(x: i32) -> i32 {
        x + 1
    }

    #[cfg(test)]
    mod tests {
        use super::*;

        #[test]
        fn it_works() {
            assert_eq!(3, add_one(2));
        }
    }

    #}

Now run `cargo test` in the top-level adder directory:

    $ cargo test
       Compiling adder v0.1.0 (file:///projects/adder)
        Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
         Running target/debug/adder-f0253159197f7841

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Wait a second, zero tests? We just added one! If we look at the output, we can see that `cargo test` in a workspace only runs the tests for the top level crate. To run tests for the other crates, we need to use the `-p` argument to indicate we want to run tests for a particular package:

    $ cargo test -p add-one
        Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
         Running target/debug/deps/add_one-abcabcabc

    running 1 test
    test tests::it_works ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

       Doc-tests add-one

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Similarly, if you choose to publish the workspace to crates.io, each crate in the workspace will get published separately.

As your project grows, consider a workspace: smaller components are easier to understand individually than one big blob of code. Keeping the crates in a workspace can make coordination among them easier if they work together and are often changed at the same time.