Skip to content

Commit 0624487

Browse files
authored
Merge pull request #3945 from epage/inherit-default
RFC: Inheriting of `default-features` in Cargo
2 parents 17a311a + ac3736d commit 0624487

1 file changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
- Feature Name: `inherit-default-features`)
2+
- Start Date: 2026-04-06
3+
- RFC PR: [rust-lang/rfcs#3945](https://github.com/rust-lang/rfcs/pull/3945)
4+
- Cargo Issue: [rust-lang/cargo#16959](https://github.com/rust-lang/cargo/issues/16959)
5+
6+
## Summary
7+
[summary]: #summary
8+
9+
Allow disabling default features locally when inheriting a dependency.
10+
```toml
11+
[workspace]
12+
13+
[workspace.dependencies]
14+
serde = "1"
15+
```
16+
```toml
17+
[package]
18+
name = "foo"
19+
20+
[dependencies]
21+
serde = { workspace = true, default-features = false }
22+
```
23+
24+
## Motivation
25+
[motivation]: #motivation
26+
27+
Say you are trying to create a package in the above workspace:
28+
```console
29+
$ cargo new default-false
30+
$ cd default-false
31+
$ cargo add serde --no-default-features
32+
error: cannot override workspace dependency with `--default-features`,
33+
either change `workspace.dependencies.serde.default-features` or
34+
define the dependency exclusively in the package's manifest
35+
$ vi Cargo.toml # manually add the above dependency
36+
$ cargo check
37+
error: failed to parse manifest at `default-false/Cargo.toml`
38+
39+
Caused by:
40+
error inheriting `serde` from workspace root manifest's `workspace.dependencies.serde`
41+
42+
Caused by:
43+
`default-features = false` cannot override workspace's `default-features`
44+
```
45+
46+
This gets in the way of universally recommending `[workspace.dependencies]`, e.g.
47+
- [#15180](https://github.com/rust-lang/cargo/issues/15180): `cargo new` should add the new package to `workspace.dependencies`
48+
- [#10608](https://github.com/rust-lang/cargo/issues/10608): `cargo add` should add the dependency to `workspace.dependencies` and use `workspace = true`
49+
- [#15578](https://github.com/rust-lang/cargo/issues/15578): lint if a dependency does not use `workspace = true`
50+
51+
Granted, there are other problems, including:
52+
- Without additional tooling support, you can't tell from looking at `git log .` in a package root all of the changes that can break compatibility
53+
- [#12546](https://github.com/rust-lang/cargo/issues/12546): cannot inherit packages renamed in `workspace.dependencies`
54+
55+
[RFC 2906](https://rust-lang.github.io/rfcs/2906-cargo-workspace-deduplicate.html) said:
56+
57+
> For now if a `workspace = true` dependency is specified then also specifying the `default-features` value is disallowed.
58+
> The `default-features` value for a directive is inherited from the `[workspace.dependencies]` declaration,
59+
> which defaults to true if nothing else is specified.
60+
61+
See also the tracking issue discussion at <https://github.com/rust-lang/cargo/issues/8415#issuecomment-727245250>
62+
63+
However, initial support didn't error or even emit an "unused manifest key" warning due to bugs.
64+
In addressing this in [#11409](https://github.com/rust-lang/cargo/pull/11409),
65+
support was added for `workspace = true, default-features = false` but in a surgical manner.
66+
The proposed mental model for this was that the `default` feature is additive like all other features though it didn't quite accomplish that.
67+
When inheriting `features` the package extends but does not override the workspace.
68+
A dependency with `default-features = true` (implicitly or explicitly) is like a package with `features = ["default"]`.
69+
So if you have a workspace dependency with `features = ["default"]` and a package with `features = []` (implicitly or explicitly),
70+
then the end result is `features = ["default"]`.
71+
72+
As this left some confusing cases,
73+
Cargo produced warnings.
74+
These warnings were turned into hard errors for Edition 2024 in [#13839](https://github.com/rust-lang/cargo/pull/13839).
75+
76+
This left us with:
77+
78+
| Workspace | Member | 1.64 behavior | 1.69 behavior | 2024 edition behavior |
79+
|:---------:|:---------:|---------------|---------------|-----------------------|
80+
| *nothing* | *nothing* | Enabled | Enabled | Enabled |
81+
| *nothing* | df=false | Enabled | **Enabled, warning that it is ignored** | **Error** |
82+
| *nothing* | df=true | Enabled | Enabled | Enabled |
83+
| df=false | *nothing* | Disabled | Disabled | Disabled |
84+
| df=false | df=false | Disabled | Disabled | Disabled |
85+
| df=false | df=true | Disabled | **Enabled** | Enabled |
86+
| df=true | *nothing* | Enabled | Enabled | Enabled |
87+
| df=true | df=false | Enabled | **Enabled, warning** | **Error** |
88+
| df=true | df=true | Enabled | Enabled | Enabled |
89+
90+
*(changes bolded)*
91+
92+
This eventually led to [#12162](https://github.com/rust-lang/cargo/issues/12162) being opened
93+
because the "features are additive" model prevents some valid cases from working, including:
94+
- `workspace.dependencies.foo = "version"`: packages cannot disable default features
95+
- `workspace.dependencies.foo = { version = "", default-features = false }`: applies to all packages, requiring `default-features = true` in all packages that do not want it
96+
97+
When discussing whether to allow inheriting of [`public`](https://doc.rust-lang.org/cargo/reference/unstable.html#public-dependency),
98+
we are starting with the answer of "no" ([#13125](https://github.com/rust-lang/cargo/pull/13125)).
99+
The thought process being that inheritance should be about consolidating shared requirements
100+
but `public` is unlikely to be inherently a shared requirement for every dependent in a workspace.
101+
This likely extends to both `default-features` and `features` and may be reason enough to deprecate inheriting them,
102+
stopping altogether in a future edition.
103+
Instead, `workspace.dependencies` should likely focus purely on inheriting of a dependency source.
104+
Before we even get there, it needs to be possible to not specify `default-features` in `workspace.dependencies`.
105+
106+
## Guide-level explanation
107+
[guide-level-explanation]: #guide-level-explanation
108+
109+
When inheriting a dependency in Edition 2024+,
110+
instead of treating `default-features` as a hypothetical entry in `features`,
111+
layer the package dependency on top of the workspace dependency on top of the default.
112+
113+
## Reference-level explanation
114+
[reference-level-explanation]: #reference-level-explanation
115+
116+
In pseudo-code, this would be:
117+
```rust
118+
let default_features = if Edition::E2024 <= package.edition {
119+
package_dep.default_features
120+
.or_else(|| workspace_dep.default_features)
121+
.unwrap_or(true)
122+
} else {
123+
// ... existing behavior
124+
};
125+
```
126+
127+
### Documentation update
128+
129+
*From [Inheriting a dependency from a workspace](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace)*
130+
131+
Along with the workspace key, dependencies can also include these keys:
132+
133+
- optional: Note that the `[workspace.dependencies]` table is not allowed to specify optional.
134+
- features: These are additive with the features declared in the [workspace.dependencies]
135+
- default-features: This overrides the value set in `[workspace.dependencies]` on Edition 2024 (requires MSRV of 1.100)
136+
137+
Inherited dependencies cannot use any other dependency key (such as version or default-features).
138+
139+
## Drawbacks
140+
[drawbacks]: #drawbacks
141+
142+
More churn on the meaning of `default-features`.
143+
However, `default-features` combined with `workspace.dependencies` likely puts this in a minority case that we can likely gloss over this in most situations.
144+
145+
## Rationale and alternatives
146+
[rationale-and-alternatives]: #rationale-and-alternatives
147+
148+
### Alternatives
149+
150+
| Workspace | Member | 1.64 behavior | 1.69 behavior | 2024 edition behavior | Proposed |
151+
|:---------:|:---------:|---------------|---------------|-----------------------|----------|
152+
| *nothing* | *nothing* | Enabled | Enabled | Enabled | Enabled |
153+
| *nothing* | df=false | Enabled | **Enabled, warning that it is ignored** | **Error** | **Disabled** |
154+
| *nothing* | df=true | Enabled | Enabled | Enabled | Enabled |
155+
| df=false | *nothing* | Disabled | Disabled | Disabled | Disabled |
156+
| df=false | df=false | Disabled | Disabled | Disabled | Disabled |
157+
| df=false | df=true | Disabled | **Enabled** | Enabled | Enabled |
158+
| df=true | *nothing* | Enabled | Enabled | Enabled | Enabled |
159+
| df=true | df=false | Enabled | **Enabled, warning** | **Error** | **Disabled** |
160+
| df=true | df=true | Enabled | Enabled | Enabled | Enabled |
161+
162+
*(changes bolded)*
163+
164+
"Workspace always wins" model
165+
- 1.64 behavior
166+
- Forces sharing of `default-features`
167+
- Has confusing cases where what you see locally (`default-features = false`) is not what happens
168+
169+
```rust
170+
let default_features = workspace.default_features
171+
.unwrap_or(true);
172+
```
173+
174+
"Almost additive" model
175+
- 1.69 behavior
176+
- Disabling `default-features` in one package requires touching all packages
177+
- Has confusing cases where what you see locally (`default-features = false`) is not what happens
178+
179+
```rust
180+
let default_features = match (workspace.default_features, package.default_features) {
181+
(Some(false), Some(true)) => Some(true),
182+
(Some(ws), _) => Some(ws),
183+
(None, _) => Some(true),
184+
};
185+
```
186+
187+
"Layered" model
188+
- **Proposed behavior**
189+
- Allows package-level control of default-features without using workspace-level control,
190+
as if support doesn't exist at the workspace
191+
192+
```rust
193+
let default_features = package_dep.default_features
194+
.or_else(|| workspace_dep.default_features)
195+
.unwrap_or(true);
196+
```
197+
198+
"Package always wins" model
199+
- Potential behavior if we remove dependency feature inheritance in a later edition
200+
201+
```rust
202+
let default_features = package.default_features.unwrap_or(true);
203+
```
204+
205+
## Prior art
206+
[prior-art]: #prior-art
207+
208+
## Unresolved questions
209+
[unresolved-questions]: #unresolved-questions
210+
211+
## Future possibilities
212+
[future-possibilities]: #future-possibilities
213+
214+
Deprecate dependency feature inheritance, removing it in a future edition.

0 commit comments

Comments
 (0)