# Cargo Workspaces As our project developsm you might find that the library crate continues to get bigger and you want to split your package further into multiple library crates. Cargo offers a feature called *workspaces* that can help manage related packages that are developed at the same time ## Creating a Workspace A *workspace* is a set of packages that share the same *Cargo.lock* and output directory. Lets explore this by a making a project using a workspace Here we will use trivial code so that the focus is on the structure of the workspace. There are mulitple ways to structure a workspace, here we will just show one common way. Here we will have a workspace containing a binary and two libraries. The binary will provide the main functionality, will depend on the two libraries. One lib will provide an `add_one` function Another lib will provide an `add_two` function These three crates will be part of the same workspace. We will start by creating a new directory for the workspace ``` $ mkdir add $ cd add ``` Now in the *add* directory, we create the *Cargo.toml* file that will config the entire workspace. This file wont have a `[package]` section, instead it will start with a `[workspace]` section. This allows us to add members to the workspace. We also make it a point to use the latest and greatest version of Cargo's resolver algorithm in our workspace by setting the `resolver` to `"2"` Here is the written out version of the toml file until this point ```toml [workspace] resolver = "2" ``` Next we will create the `adder` binary crate We do this by running `cargo new` within the *add* directory ``` $ cargo new adder Creating binary (application) `adder` package Adding `adder` as member of workspace at `file:///projects/add` ``` By running the `cargo new` command inside a workspace it automatically adds the newly created package to the `members` key in the `[workspace]` definition in the workspace `Cargo.toml` It will look like this ```toml [workspace] resolver = "2" members = ["adder"] ``` Now at this point we can build the workspace by rnning `cargo build` The files in the *add* directory should now look like this ``` ├── Cargo.lock ├── Cargo.toml ├── adder │ ├── Cargo.toml │ └── src │ └── main.rs └── target ``` The workspace has one *target* directory at the top level that compled artifacts will be placed into The `adder` package doesn't have its own *target* directory. Even if we were to run `cargo build` from inside the *addre* dir, the compiled artifacts would still end up in *add/target* rather than *add/adder/target* Cargo structs the *target* directory in a workspace like this because the crates in a workspace are meant to depend on each other. Without this we would have to repeat compiled artifacts and this would cause unnecessary rebuilding. So we share one *target* directory to avoid this. ## Creating the Second Package in the Wrokspace Now lets add another package to the workspace, we will call it First we will change the top level *Cargo.toml* to specify the *add_one* path in the `members` list Here is the updated toml ```toml [workspace] resolver = "2" members = ["adder", "add_one"] ``` Now lets generate a new library crate named `add_one` ``` $ cargo new add_one --lib Creating library `add_one` package Adding `add_one` as member of workspace at `file:///projects/add` ``` Now the *add* directory should look like this ``` ├── Cargo.lock ├── Cargo.toml ├── add_one │ ├── Cargo.toml │ └── src │ └── lib.rs ├── adder │ ├── Cargo.toml │ └── src │ └── main.rs └── target ``` In *add_one/src/lib.rs* lets add the `add_one` function ```rust pub fn add_one(x: i32) -> i32 { x + 1 } ``` Now we need to have the `adder` package with our binary depend on the `add_one` package that has our library. We need to add a path dependency on `add_one` to *adder/Cargo.toml* ```toml [dependencies] add_one = { path = "../add_one" } ``` Cargo doesnt assume that crates in a workspace will depend on each other. We must explicit about the dependency relationships. Now lets use the `add_one` function in the `adder` crate. You modify the *adder/src/main.rs* file and change the `main` function to call the `add_one` function Here is the updated `main` function in the binary crate ```rust fn main() { let num = 10; println!("Hello, world! {num} plus one is {}!", add_one::add_one(num)); } ``` Now we can build the workspace from the top-level *add* directory ``` $ cargo build Compiling add_one v0.1.0 (file:///projects/add/add_one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s ``` In order to run the binary crate from the *add* directory have have to specify which package in the workspace we want to run. We do this by using the `-p` argument and the package name with `cargo run` ``` $ cargo run -p adder Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s Running `target/debug/adder` Hello, world! 10 plus one is 11! ``` This runs the `main` function in *adder/src/main.rs*, which depends on the `add_one` crate ### Depending on an External Package in a Wrokspace Notice that the workspace only has one *Cargo.lock* file at the top level. This ensure that all crates are using the same version of all dependencies. If we add the `rand` package to the *adder/Cargo.toml* and *add_one/Cargo.toml* files Cargo will resolve both of thos to one version of `rand` and record that in the one *Cargo.lock*. This ensures that all the crates in the workspace use the same dependencies, this also ensures that all crates will always be compatible with each other. Lets demonstrate how to add the `rand` crate to the `[dependencies]` section in the *add_one/Cargo.toml* file so we can use the `rand` crate in the `add_one` crate ```toml [dependencies] rand = "0.8.5" ``` Now we can add `use rand;` to the *add_one/src/lib.rs* file. Then build the whole workspace in the *add* directory will bring in and compile the `rand` crate. We will get a warning about a unused `rand` becasue we dont refer to it after we bring it into scope ``` $ cargo build Updating crates.io index Downloaded rand v0.8.5 --snip-- Compiling rand v0.8.5 Compiling add_one v0.1.0 (file:///projects/add/add_one) warning: unused import: `rand` --> add_one/src/lib.rs:1:5 | 1 | use rand; | ^^^^ | = note: `#[warn(unused_imports)]` on by default warning: `add_one` (lib) generated 1 warning (run `cargo fix --lib -p add_one` to apply 1 suggestion) Compiling adder v0.1.0 (file:///projects/add/adder) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s ``` Now the top level *Cargo.lock* now contains info about the dependency of `add_one` 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 thier *Cargo.toml* files as well For example if we add `use rand;` to the *adder/src/main.rs* file for the `adder` package we would get an error ``` $ cargo build --snip-- Compiling adder v0.1.0 (file:///projects/add/adder) error[E0432]: unresolved import `rand` --> adder/src/main.rs:2:5 | 2 | use rand; | ^^^^ no external crate `rand` ``` In order to fix this we need to edit the *Cargo.toml* file for the `adder` package and indicate that `rand` is a dependency for it as well. Rebuilding the `adder` package with the eidt will now add the `rand` crate to the list of dependencies for `adder` in *Cargo.lock*. No additional copies of `rand` will be downloaded. Cargo will ensure that every crate in every package in the workspace using the `rand` package will be using the same version as log as they specify compatible versions of `rand`. This saves us space and ensures that the crates in the workspace will be compatible with each other. If crates in the workspace specify incompatible verions of the same dependency, Cargo will attempt to resolve each of them but it will still try to resolve it with as few versions as possible ### Adding a Test to a Workspace For another enchancement lets add a test of the `add)one::add_one` function within the `add_one` crate Here is the updated *add_one/src/lib.rs* ```rust 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 if we run `cargo test` in the top level *add* directory in a workspace structured like thisone will run the tests for all the crates in the workspace. Here is what the output will look like this ``` $ cargo test Compiling add_one v0.1.0 (file:///projects/add/add_one) Compiling adder v0.1.0 (file:///projects/add/adder) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s Running unittests src/lib.rs (target/debug/deps/add_one-f0253159197f7841) 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 Running unittests src/main.rs (target/debug/deps/adder-49979ff40686fa8e) running 0 tests test result: ok. 0 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 ``` The first section shows that the `it_works` test in the `add_one` crate pased The next section shows that zero tests were found in the `adder` crate. Thhe last section shows zero documentation tests were found in the `add_one` crate. We can also run tests for a particular crate in a workspace from the top-level directory. We do this by adding the `-p` flag and specifying th name of the crate we want to test. Here is an example of this ``` $ cargo test -p add_one Finished `test` profile [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 ``` This shows that `cargo test` only ran tests for the `add_one` crate and didn't run the `adder` crate tests. If you publish the crates in the workspace to [crates.io](https://crates.io), each crate in the workspace will need to be published separately. Just like `cargo test`, you can publish a particular crate in the workspace by using the `-p` flag and specifying the name of the crate we want to publish. As your project grows, consider using a workspace. It makes it easier to understand smaller, individual components than one big blog of code. Also by keeping the crates in a workspace can make coordination between crates easier if they are often changed at the same time.