Safeguard
Developer Tools•

Cargo workspaces: shared dependencies and a single Cargo.lock

How a Cargo workspace shares one Cargo.lock and one set of dependency versions through workspace.dependencies, with the exact inheritance syntax.

Safeguard Security Team
3 min read

A Cargo workspace groups several crates under one root. They share a single Cargo.lock and one target directory. With the workspace.dependencies table you declare each shared crate once in the root manifest, and members inherit it with workspace = true, so versions cannot drift between crates.

What the workspace shares

The Cargo reference lists the basics. Common commands run across all members, for example cargo check --workspace. All packages share one Cargo.lock in the workspace root, and one output directory, target, by default in the root. Sections such as patch, replace and profile are only read from the root manifest and are ignored in members.

For a root with no package of its own, called a virtual manifest, set the resolver explicitly:

[workspace]
members = ["crates/*"]
resolver = "3"

The docs recommend keeping members in a flat directory like crates and using a glob.

Declare dependencies once

In the root Cargo.toml:

[workspace.dependencies]
rand = "0.8.5"
regex = { version = "1.6.0", default-features = false, features = ["std"] }
serde = { version = "1.0.190", default-features = true }

In a member:

[dependencies]
regex = { workspace = true, features = ["unicode"] }
serde.workspace = true

[dev-dependencies]
rand.workspace = true

Features in the member add to the workspace features. Workspace dependencies cannot be marked optional. The docs note that how a member can turn off default features depends on edition and Rust version, so test that case on your toolchain.

Share package metadata and lints too

The same pattern works for package keys such as version, license and repository through workspace.package, and for lints through workspace.lints. In a member you write version.workspace = true or workspace = true under lints. A workspace lint table can, for example, set unsafe_code to forbid for every crate.

Why this matters for supply chain review

Two things become easier:

  • One Cargo.lock means one place to review when a transitive crate changes.
  • One workspace.dependencies table means a version bump is a one line diff, with every member affected.

It also makes questions like "which version of serde do we ship" answerable in seconds. For libraries, see whether to commit Cargo.lock for a library. For binaries and services in a workspace, commit it.

Limits

A workspace forces one resolution for the whole tree. If two crates need incompatible major versions of something, Cargo can include both, but that is a signal to look at why. A workspace also does not isolate crates from each other at build time, so a crate that needs a very different toolchain may be better as its own project.

Safeguard dependency and SBOM scanning can list the crates in each part of a repository and flag known vulnerabilities. You can try a free scan at gold.safeguard.sh/scan.

Click through it at your own pace, or open it full screen.

Quick check

Where does Cargo.lock live in a workspace?

In the workspace root. All members share it.

Can a member override a workspace dependency version?

Inheriting with workspace = true takes the version from the root. To use a different version, declare the dependency normally in that member and expect two versions in the tree.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.