This post describes how I set up Cargo workspaces.


A workspace is composed of smaller crates that are all compiled together. This is useful for large projects that have clear boundaries between components.

It also makes it possible to publish crates to crates.io separately!

The Recipe

The folder structure looks like this:

workspace
├── common
├── server
└── client

We create a crate for each of the components of our project.

We will see how the server and client crates share code by using the common crate & define shared dependencies in the workspace so that the same version is used in all of the crates.

How to do it

This section describes how to bootstrap a workspace.

Create the workspace

Our project will be called bazinga.

We will pretend that this project implements a client and server that share some code.

cargo new bazinga
rm -rf bazinga/src

We remove the src folder because we will be using a workspace, not an application or library.

Create the crates

Create the child crates within the workspace:

cd bazinga
cargo new server
cargo new client
cargo new common

Add workspace members

[workspace]
members = [
    "server",
    "client",
    "common",
]

Connect the crates

We need to add the common crate as a dependency to the server and client crates.

In both server/Cargo.toml and client/Cargo.toml:

[dependencies]
common = { path = "../common" }

To verify that it is set up properly, run cargo check in the workspace root.

The common namespace is now available in both the server and client crates.

Define dependencies

Normally you would specify versions for dependencies that you use in the Cargo.toml file of the crate that uses them.

However, if you want to use the same version of a dependency in multiple crates, you can specify it in the workspace Cargo.toml file.

See inheriting a dependency from a workspace for details on how to share a dependency between crates.

Workspace

You will define workspace.dependencies and pull them into the crates that need them.

In workspace/Cargo.toml:

[workspace]
members = [
    "server",
    "client",
    "common",
]

[workspace.dependencies]
tokio = { version = "1.26.0", features = ["full"] }

Workspace members

In the crates which inherit dependencies from the workspace, you will specify a dependency using the syntax <dependency>.workspace = true.

For example, in server/Cargo.toml:

[package]
name = "server"
version = "0.1.0"
edition = "2021"

[dependencies]
common = { path = "../common" }
tokio.workspace = true

Run cargo check again in the workspace root to verify that it is set up properly.

Conclusion

We’ve seen how to set up a workspace with multiple crates.

Library code can be shared between the workspace members.

It is possible to define a dependency’s version once in the workspace and then inherit it in the crates that need it.

References