Skip to main content

Rust Monorepo Setup (Cargo Workspaces, Mac)

In this article, I'm going to show you how to create a monorepo with Rust using Cargo Workspaces.

info

These instructions were written and tested on a Mac.

Step 1. Project Setup

mkdir -p ~/projects/rust/rust-monorepo-demo
cd ~/projects/rust/rust-monorepo-demo

Step 2. Add a virtual manifest file

  • Create a Cargo.toml file in the root of the project.
touch Cargo.toml

If there is no root src folder or package info, then the root Cargo.toml is referred to as a "virtual manifest" file.

  • Open the file in VS Code (code .)
  • Replace the contents (if any), with the contents below and save it:
[workspace]

members = [
"tools_lib",
"alpha_app",
"beta_app"
]

The monorepo is going to contain two apps and a shared library (tools_lib).

Step 3. Create workspace packages (members)

In this step you will create the three members of the workspace:

  • tools_lib - a library used by the other members
  • alpha_app - a binary that uses the library
  • beta_app - another binary that also uses the library

Create tools_lib

  • Create the tools_lib package
  • Use the --vcs none flag so that a git repo isn't created just for the child package
  • Because it's a library we need to use the --lib flag
  • Don't worry about errors referring to other packages that we haven't created yet
cargo new --vcs none --lib tools_lib

Create alpha_app

  • Create the alpha_app binary package
  • In this case there is no lib flag because we're building a binary executable
cargo new --vcs none alpha_app

Create beta_app

  • Create the beta_app binary package
cargo new --vcs none beta_app

Tree view

If you have tree installed, run it and you should see a file layout like this:

% tree
.
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── alpha_app
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── beta_app
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── tools_lib
├── Cargo.toml
└── src
└── lib.rs

Step 4. Run cargo build

  • To build everything in the monorepo run this command:
cargo build

You should see that everything built, but the only target folder is in the root.

If you run tree again you should see this snippet in the middle of the results. It shows that alpha_app and beta_app were created in the root target:

├── target
│ ├── CACHEDIR.TAG
│ └── debug
│ ├── alpha_app
│ ├── alpha_app.d
│ ├── beta_app
│ ├── beta_app.d
│ ├── build

Step 5. Fix the workspace.resolver warning

When you ran cargo build you may have seen a warning about a workspace.resolver parameter.

To fix that, do the following:

  • In the root Cargo.toml file add the resolver line in the [workspace] section and save the file:
[workspace]
resolver = "2"
  • Before rebuilding, clear out the old targets with this command:
cargo clean
  • Rebuild and this time the warning should hopefully be gone:
cargo build

Step 6. Run a package

  • Try running this command:
cargo run

You should see an error like this:

error: `cargo run` could not determine which binary to run. 
Use the `--bin` option to specify a binary, or the `default-run` manifest key.
available binaries: alpha_app, beta_app

You could use the --bin flag as the error suggests. But you can also use the -p flag.

  • Run this command to run just alpha_app:
cargo run -p alpha_app

The -p stands for package, which you can see if you look at the help for the cargo run command:

cargo run --help

Using the --bin flag will do the same:

cargo run --bin alpha_app

Step 7. Add workspace dependencies

So far everything has been running in isolation. Now we can add some dependencies:

  • In the alpha_app folder edit the Cargo.toml file
  • Add this line in the dependencies section and save it:
tools_lib = { path = "../tools_lib"}
  • Do the same for the Cargo.toml file in the beta_app folder

Step 8. Use a workspace function

When you created tools_lib, a default public function that adds numbers together should have been added.

  • Check tools_lib/src/lib.rs just to be sure

Update alpha_app

  • Edit alpha_app/src/main.rs to use the tools library
  • Change it to look like this and save the file
use tools_lib::add;

fn main() {
let x = 10;
let y = 20;
let result = add(x, y);
println!("Alpha: {x} + {y} = {result}");
}
  • Run the app to verify the results:
cargo run -p alpha_app

Update beta_app

  • Repeat the last set of steps for the beta_app package
  • Change the numbers (x and y) to get a different result
  • Change the print statement to say Beta instead of Alpha
  • Verify that you can run it and get the expected output
cargo run -p beta_app

If you just want to see the output, you can add the --quiet flag:

cargo run --quiet -p beta_app

Example Repo

You can find an example of the repo created for this article here:

Makefile

I've added to the example project a Makefile with some instructions for using that to run the project.

Conclusion

Congratulations! In this article you learned how to:

  • Create a rust monorepo using cargo workspaces
  • How to setup Cargo.toml as a virtual manifest for a monorepo
  • How to add binary and lib packages to a rust monorepo
  • How to create dependencies to local libraries in a monorepo
  • How to run individual packages within a monorepo

References