RFA-110 · Case file with fixtures · Case 82 of 694 · Cargo workspace evidence
Why Cargo Workspace Inheritance Fails When the Root Key Is Missing
A member's key.workspace=true is a lookup into a matching workspace root table, not a default-value request. Define the root policy explicitly and verify commands from the real workspace root.
- Reviewed
- Rust
- Rust 1.98.1, Cargo 1.98.1
- Targets
- all targets
- Profiles
- metadata, check, dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- A member's workspace=true field references a matching value in a specific root table; it does not request Cargo's default value.
- First discriminating check
- Use Cargo metadata to identify the actual workspace root, then inspect the exact dotted root key named in the cause chain.
Workspace inheritance removes repeated manifest values, but it does not invent them. The failing workspace has a member declaring:
[package]
edition.workspace = true
The root manifest has a [workspace] table but no [workspace.package] edition. Cargo follows the inheritance request to the root, finds no value, and reports that workspace.package.edition was not defined.
workspace=true is a reference
The workspace package table documentation lists package fields that members may inherit. The root defines values under [workspace.package]; each member opts in per field with field.workspace = true.
I read it as a reference:
member: edition.workspace = true
|
v
root: [workspace.package]
edition = "2024"
It is not “use Cargo's default edition.” If I want the member to choose directly, I write edition = "2024" in that member. If I want shared policy, the root must say what the policy is.
The edition field reference also matters because omitting an edition can select an old default for a package. Explicit inheritance avoids accidental ambiguity only when the root value exists.
The repaired workspace makes policy visible
The repaired root manifest adds:
[workspace.package]
edition = "2024"
The member can now inherit it. Real workspaces often centralize version, authors, license, repository, rust-version, and edition in the same way.
I do not inherit a field only to reduce lines. I inherit it when members genuinely share a release or compatibility policy. A workspace containing tools with different MSRV promises may need explicit member values.
Virtual and rooted workspaces differ
A virtual workspace root has [workspace] but no [package]. Shared package metadata still belongs in [workspace.package], not an imaginary root package.
A root package can be both a package and workspace root. Even then, [package] edition = "2024" is not the same location as [workspace.package] edition = "2024". Members asking for workspace inheritance look in the latter table.
This is a frequent source of confusion because both values can appear in one file and describe similar concepts for different consumers.
The discovered workspace root matters
Cargo searches parent directories to find a workspace root, unless manifest structure or command options change the selection. A crate copied into another repository can begin inheriting from a different root or fail because its intended root is absent.
I run cargo metadata from the failing environment and inspect workspace_root and workspace_members. This is more reliable than assuming the nearest visible Cargo.toml is the one Cargo selected.
Nested workspaces, excluded members, generated checkouts, and packaging can make local and CI layouts differ. A member manifest with .workspace = true is intentionally not standalone; the package workflow must preserve its workspace context or rewrite metadata for publication as Cargo expects.
Dependency inheritance has another table
Workspace dependencies live under [workspace.dependencies], and members opt in inside their dependency tables. Package metadata lives under [workspace.package]. Lints have their own workspace table.
I keep these categories separate because a root dependency entry cannot satisfy version.workspace = true, and package metadata cannot satisfy a dependency lookup. The final Cargo error usually names the exact missing dotted path; that path is the best clue.
Inherited values affect every opting-in member
A root edit can change many published packages at once. Raising the inherited edition or rust-version is therefore a workspace-wide compatibility operation for all members that opt in. I list those members before the change and run their individual package checks, not only the default workspace members.
For release automation, I also inspect cargo package --list and the normalized manifest produced for publication. This catches assumptions that work only because neighboring workspace files are present in the checkout.
My debugging sequence
When inheritance fails, I do this:
- Read the last cause and copy the full missing root key.
- Find the actual workspace root using Cargo metadata.
- Check that the root uses the correct table: package, dependencies, or lints.
- Decide whether the value is a shared policy or should stay local to the member.
- Add the explicit root value or replace inheritance with an explicit member value.
- Run metadata, check, tests, and packaging from a clean checkout.
The syntax is small, but it expresses ownership. The member says “this value belongs to the workspace policy.” The root must then provide that policy. When both sides are explicit, inheritance reduces drift without making the manifest mysterious.