From 25e92bbd2da24f3cba481422ee6b42548787254a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 6 Apr 2026 14:19:22 -0500 Subject: [PATCH 1/9] Add skeleton --- text/0000-inherit-default-features.md | 103 ++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 text/0000-inherit-default-features.md diff --git a/text/0000-inherit-default-features.md b/text/0000-inherit-default-features.md new file mode 100644 index 00000000000..fdc40810e86 --- /dev/null +++ b/text/0000-inherit-default-features.md @@ -0,0 +1,103 @@ +- Feature Name: (fill me in with a unique ident, `my_awesome_feature`) +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +## Summary +[summary]: #summary + +One paragraph explanation of the feature. + +## Motivation +[motivation]: #motivation + +Any changes to Rust should focus on solving a problem that users of Rust are having. +This section should explain this problem in detail, including necessary background. + +It should also contain several specific use cases where this feature can help a user, and explain how it helps. +This can then be used to guide the design of the feature. + +This section is one of the most important sections of any RFC, and can be lengthy. + +## Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: + +- Introducing new named concepts. +- Explaining the feature largely in terms of examples. +- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. +- Discuss how this impacts the ability to read, understand, and maintain Rust code. Code is read and modified far more often than written; will the proposed feature make code easier to maintain? + +For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. + +## Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This is the technical portion of the RFC. Explain the design in sufficient detail that: + +- Its interaction with other features is clear. +- It is reasonably clear how the feature would be implemented. +- Corner cases are dissected by example. + +The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. + +## Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +## Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? +- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? + +## Prior art +[prior-art]: #prior-art + +Discuss prior art, both the good and the bad, in relation to this proposal. +A few examples of what this can include are: + +- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? +- For community proposals: Is this done by some other community and what were their experiences with it? +- For other teams: What lessons can we learn from what other communities have done here? +- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. + +This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. +If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. + +Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. +Please also take into consideration that rust sometimes intentionally diverges from common language features. + +## Unresolved questions +[unresolved-questions]: #unresolved-questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +## Future possibilities +[future-possibilities]: #future-possibilities + +Think about what the natural extension and evolution of your proposal would +be and how it would affect the language and project as a whole in a holistic +way. Try to use this section as a tool to more fully consider all possible +interactions with the project and language in your proposal. +Also consider how this all fits into the roadmap for the project +and of the relevant sub-team. + +This is also a good place to "dump ideas", if they are out of scope for the +RFC you are writing but otherwise related. + +If you have tried and cannot think of any future possibilities, +you may simply state that you cannot think of anything. + +Note that having something written down in the future-possibilities section +is not a reason to accept the current or a future RFC; such notes should be +in the section on motivation or rationale in this or subsequent RFCs. +The section merely provides additional information. From dffd307adf1e7bae8868b442fa25987c5c7df4c6 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 6 Apr 2026 16:11:03 -0500 Subject: [PATCH 2/9] feat: Initial draft --- text/0000-inherit-default-features.md | 243 +++++++++++++++++++------- 1 file changed, 177 insertions(+), 66 deletions(-) diff --git a/text/0000-inherit-default-features.md b/text/0000-inherit-default-features.md index fdc40810e86..5e0a8c3469f 100644 --- a/text/0000-inherit-default-features.md +++ b/text/0000-inherit-default-features.md @@ -1,103 +1,214 @@ -- Feature Name: (fill me in with a unique ident, `my_awesome_feature`) -- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Feature Name: `inherit-default-features`) +- Start Date: 2026-04-06 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +- Cargo Issue: [rust-lang/cargo#0000](https://github.com/rust-lang/cargo/issues/0000) ## Summary [summary]: #summary -One paragraph explanation of the feature. +Allow disabling default features locally when inhering a package. +```toml +[workspace] -## Motivation -[motivation]: #motivation +[workspace.dependencies] +serde = "1" +``` +```toml +[package] +name = "foo" -Any changes to Rust should focus on solving a problem that users of Rust are having. -This section should explain this problem in detail, including necessary background. +[dependencies] +serde = { workspace = true, default-features = false } +``` -It should also contain several specific use cases where this feature can help a user, and explain how it helps. -This can then be used to guide the design of the feature. +## Motivation +[motivation]: #motivation -This section is one of the most important sections of any RFC, and can be lengthy. +Say you are trying to create a package in the above workspace: +```console +$ cargo new default-false +$ cd default-false +$ cargo add serde --no-default-features +error: cannot override workspace dependency with `--default-features`, +either change `workspace.dependencies.serde.default-features` or +define the dependency exclusively in the package's manifest +$ vi Cargo.toml # manually add the above dependency +$ cargo check +error: failed to parse manifest at `default-false/Cargo.toml` + +Caused by: + error inheriting `serde` from workspace root manifest's `workspace.dependencies.serde` + +Caused by: + `default-features = false` cannot override workspace's `default-features` +``` + +This gets in the way of universally recommending `[workspace.dependencies]`, e.g. +- [#15180](https://github.com/rust-lang/cargo/issues/15180): `cargo new` should add the new package to `workspace.dependencies` +- [#10608](https://github.com/rust-lang/cargo/issues/10608): `cargo add` should add the dependency to `workspace.dependencies` and use `workspace = true` +- [#15578](https://github.com/rust-lang/cargo/issues/15578): lint if a dependency does not use `workspace = true` + +Granted, there are other problems, including: +- 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 +- [#12546](https://github.com/rust-lang/cargo/issues/12546): cannot inherit packages renamed in `workspace.dependencies` + +[RFC 2906](https://rust-lang.github.io/rfcs/2906-cargo-workspace-deduplicate.html) said: + +> For now if a `workspace = true` dependency is specified then also specifying the `default-features` value is disallowed. +> The `default-features` value for a directive is inherited from the `[workspace.dependencies]` declaration, +> which defaults to true if nothing else is specified. + +See also the tracking issue discussion at + +However, initial support didn't error or even emit an "unused manifest key" warning due to bugs. +In addressing this in [#11409](https://github.com/rust-lang/cargo/pull/11409), +support was added for `workspace = true, default-features = false` but in a surgical manner. +The proposed amental model for this was that the `default` feature is additive like all other features though it didn't quite accomplish that. +When inheriting `features` the package extends but does not override the workspace. +A dependency with `default-features = true` (implicitly or explicitly) is like a package withb `features = ["default"]`. +So if you have a workspace dependency with `features = ["default"]` and a package with `features = []` (implicitly or explicitly), +then the end result is `features = ["default"]`. + +As this left some confusing cases, +Cargo produced warnings. +These warnings were turned into hard errors for Edition 2024 in [#13839](https://github.com/rust-lang/cargo/pull/13839).0 + +This left us with: + +| Workspace | Member | 1.64 behavior | 1.69 behavior | 2024 edition behavior | +|:---------:|:---------:|---------------|---------------|-----------------------| +| *nothing* | *nothing* | Enabled | Enabled | Enabled | +| *nothing* | df=false | Enabled | **Enabled, warning that it is ignored** | **Error** | +| *nothing* | df=true | Enabled | Enabled | Enabled | +| df=false | *nothing* | Disabled | Disabled | Disabled | +| df=false | df=false | Disabled | Disabled | Disabled | +| df=false | df=true | Disabled | **Enabled** | Enabled | +| df=true | *nothing* | Enabled | Enabled | Enabled | +| df=true | df=false | Enabled | **Enabled, warning** | **Error** | +| df=true | df=true | Enabled | Enabled | Enabled | + +*(changes bolded)* + +This eventually led to [#12162](https://github.com/rust-lang/cargo/issues/12162) being opened +because the "features are additive" model prevents some valid cases from working, including: +- `workspace.dependencies.foo = "version"`: packages cannot disable default features +- `workspace.dependencies.foo = { version = "", default-features = false }`: applies to all packages, requiring `default-features = true` in all packages that do not want it + +When discussing whether to allow inheriting of [`public`](https://doc.rust-lang.org/cargo/reference/unstable.html#public-dependency), +we are starting with the answer of "no" ([#13125](https://github.com/rust-lang/cargo/pull/13125)). +The thought process being that inheritance should be about consolidating shared requirements +but `public` is unlikely to be inherently a shared requirement for every dependent in a workspace. +This likely extends to both `default-features` and `features` and may be reason enough to deprecate inheriting them, +stopping altogether in a future edition. +Instead, `workspace.dependencies` should likely focus purely on inheriting of a dependency source. +Before we even get there, it needs to be possible to not specify `default-features` in `workspace.dependencies`. ## Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: - -- Introducing new named concepts. -- Explaining the feature largely in terms of examples. -- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. -- Discuss how this impacts the ability to read, understand, and maintain Rust code. Code is read and modified far more often than written; will the proposed feature make code easier to maintain? - -For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. +When inheriting a dependency in Edition 2024+, +instead of treating `default-features` as a hypothetical entry in `features`, +layer the package dependency on top of the workspace dependency on top of the default. ## Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This is the technical portion of the RFC. Explain the design in sufficient detail that: +In pseudo-code, this would be: +```rust +let default_features = if Edition::E2024 <= package.edition { + package_dep.default_features + .or_else(|| workspace_dep.default_features) + .unwrap_or(true) +} else { + // ... existing behavior +}; +``` + +### Documentation update + +*From [Inheriting a dependency from a workspace](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#inheriting-a-dependency-from-a-workspace)* + +Along with the workspace key, dependencies can also include these keys: -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. +- optional: Note that the `[workspace.dependencies]` table is not allowed to specify optional. +- features: These are additive with the features declared in the [workspace.dependencies] +- default-features: This overrides the value set in `[workspace.dependencies]` on Edition 2024 (requires MSRV of 1.100) -The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. +Inherited dependencies cannot use any other dependency key (such as version or default-features). ## Drawbacks [drawbacks]: #drawbacks -Why should we *not* do this? +More churn on the meaning of `default-features`. +However, `default-features` combined with `workspace.dependencies` likely puts this in a minority case that we can likely gloss over this in most situations. ## Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? -- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? +### Alternatives + +| Workspace | Member | 1.64 behavior | 1.69 behavior | 2024 edition behavior | Proposed | +|:---------:|:---------:|---------------|---------------|-----------------------|----------| +| *nothing* | *nothing* | Enabled | Enabled | Enabled | Enabled | +| *nothing* | df=false | Enabled | **Enabled, warning that it is ignored** | **Error** | **Disabled** | +| *nothing* | df=true | Enabled | Enabled | Enabled | Enabled | +| df=false | *nothing* | Disabled | Disabled | Disabled | Disabled | +| df=false | df=false | Disabled | Disabled | Disabled | Disabled | +| df=false | df=true | Disabled | **Enabled** | Enabled | Enabled | +| df=true | *nothing* | Enabled | Enabled | Enabled | Enabled | +| df=true | df=false | Enabled | **Enabled, warning** | **Error** | **Disabled** | +| df=true | df=true | Enabled | Enabled | Enabled | Enabled | + +*(changes bolded)* + +"Workspace always wins" model +- 1.64 behavior +- Forces sharing of `default-features` +- Has confusing cases where what you see locally (`default-features = false`) is not what happens + +```rust +let default_features = workspace.default_features + .unwrap_or(true); +``` + +"Almost additive" model +- 1.69 behavior +- Disabling `default-features` in one package requires touching all packages +- Has confusing cases where what you see locally (`default-features = false`) is not what happens + +```rust +let default_features = match (workspace.default_features, package.default_features) { + (Some(false), Some(true)) => Some(true), + (Some(ws), _) => Some(ws), + (None, _) => Some(true), +}; +``` + +"Layered" model +- **Proposed behavior** +- Allows package-level control of default-features without using workspace-level control, + as if support doesn't exist at the workspace + +```rust +let default_features = package_dep.default_features + .or_else(|| workspace_dep.default_features) + .unwrap_or(true); +``` + +"Package always wins" model +- Potential behavior if we remove dependency feature inheritance in a later edition + +```rust +let default_features = package.default_features.unwrap_or(true); +``` ## Prior art [prior-art]: #prior-art -Discuss prior art, both the good and the bad, in relation to this proposal. -A few examples of what this can include are: - -- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? -- For community proposals: Is this done by some other community and what were their experiences with it? -- For other teams: What lessons can we learn from what other communities have done here? -- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. - -This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. -If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. - -Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. -Please also take into consideration that rust sometimes intentionally diverges from common language features. - ## Unresolved questions [unresolved-questions]: #unresolved-questions -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? - ## Future possibilities [future-possibilities]: #future-possibilities -Think about what the natural extension and evolution of your proposal would -be and how it would affect the language and project as a whole in a holistic -way. Try to use this section as a tool to more fully consider all possible -interactions with the project and language in your proposal. -Also consider how this all fits into the roadmap for the project -and of the relevant sub-team. - -This is also a good place to "dump ideas", if they are out of scope for the -RFC you are writing but otherwise related. - -If you have tried and cannot think of any future possibilities, -you may simply state that you cannot think of anything. - -Note that having something written down in the future-possibilities section -is not a reason to accept the current or a future RFC; such notes should be -in the section on motivation or rationale in this or subsequent RFCs. -The section merely provides additional information. +Deprecate dependency feature inheritance, removing it in a future edition. From 24704459fd8ab1e27b108116aabdea4db25f6813 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 6 Apr 2026 16:12:25 -0500 Subject: [PATCH 3/9] chore: Rename file for RFC # --- ...herit-default-features.md => 3945-inherit-default-features.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-inherit-default-features.md => 3945-inherit-default-features.md} (100%) diff --git a/text/0000-inherit-default-features.md b/text/3945-inherit-default-features.md similarity index 100% rename from text/0000-inherit-default-features.md rename to text/3945-inherit-default-features.md From 051629936f9432f63ec8c340b62e70ed4d9bfaae Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 6 Apr 2026 16:12:49 -0500 Subject: [PATCH 4/9] chore: Update RFC# in text --- text/3945-inherit-default-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3945-inherit-default-features.md b/text/3945-inherit-default-features.md index 5e0a8c3469f..5980dc98be4 100644 --- a/text/3945-inherit-default-features.md +++ b/text/3945-inherit-default-features.md @@ -1,6 +1,6 @@ - Feature Name: `inherit-default-features`) - Start Date: 2026-04-06 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3945](https://github.com/rust-lang/rfcs/pull/3945) - Cargo Issue: [rust-lang/cargo#0000](https://github.com/rust-lang/cargo/issues/0000) ## Summary From 37f57514ff43af2ac05625b123fb9fc7156e898a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 6 Apr 2026 16:14:56 -0500 Subject: [PATCH 5/9] style: Fix spelling --- text/3945-inherit-default-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3945-inherit-default-features.md b/text/3945-inherit-default-features.md index 5980dc98be4..ef26839eb9f 100644 --- a/text/3945-inherit-default-features.md +++ b/text/3945-inherit-default-features.md @@ -6,7 +6,7 @@ ## Summary [summary]: #summary -Allow disabling default features locally when inhering a package. +Allow disabling default features locally when inheriting a package. ```toml [workspace] From 46ec9bb333e34cbb7a248e373fafb66d00a2f047 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 6 Apr 2026 16:15:41 -0500 Subject: [PATCH 6/9] fix(summary): Inheriting deps, not packages --- text/3945-inherit-default-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3945-inherit-default-features.md b/text/3945-inherit-default-features.md index ef26839eb9f..6f2a66622aa 100644 --- a/text/3945-inherit-default-features.md +++ b/text/3945-inherit-default-features.md @@ -6,7 +6,7 @@ ## Summary [summary]: #summary -Allow disabling default features locally when inheriting a package. +Allow disabling default features locally when inheriting a dependency. ```toml [workspace] From dbb4f2835870352548f6e72f63138dbe5574459b Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 7 Apr 2026 15:11:49 -0500 Subject: [PATCH 7/9] style(motivation): Fix a typo --- text/3945-inherit-default-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3945-inherit-default-features.md b/text/3945-inherit-default-features.md index 6f2a66622aa..2a4e4aa8d23 100644 --- a/text/3945-inherit-default-features.md +++ b/text/3945-inherit-default-features.md @@ -63,7 +63,7 @@ See also the tracking issue discussion at Date: Tue, 7 Apr 2026 15:12:21 -0500 Subject: [PATCH 8/9] style(motivation): Fix a typo --- text/3945-inherit-default-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3945-inherit-default-features.md b/text/3945-inherit-default-features.md index 2a4e4aa8d23..3766043b94a 100644 --- a/text/3945-inherit-default-features.md +++ b/text/3945-inherit-default-features.md @@ -65,7 +65,7 @@ In addressing this in [#11409](https://github.com/rust-lang/cargo/pull/11409), support was added for `workspace = true, default-features = false` but in a surgical manner. The proposed mental model for this was that the `default` feature is additive like all other features though it didn't quite accomplish that. When inheriting `features` the package extends but does not override the workspace. -A dependency with `default-features = true` (implicitly or explicitly) is like a package withb `features = ["default"]`. +A dependency with `default-features = true` (implicitly or explicitly) is like a package with `features = ["default"]`. So if you have a workspace dependency with `features = ["default"]` and a package with `features = []` (implicitly or explicitly), then the end result is `features = ["default"]`. From 51af31f44b4e9694e1277082752d62ab0ec329f8 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 7 Apr 2026 15:12:38 -0500 Subject: [PATCH 9/9] style(motivation): Fix a typo --- text/3945-inherit-default-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3945-inherit-default-features.md b/text/3945-inherit-default-features.md index 3766043b94a..7862f978f20 100644 --- a/text/3945-inherit-default-features.md +++ b/text/3945-inherit-default-features.md @@ -71,7 +71,7 @@ then the end result is `features = ["default"]`. As this left some confusing cases, Cargo produced warnings. -These warnings were turned into hard errors for Edition 2024 in [#13839](https://github.com/rust-lang/cargo/pull/13839).0 +These warnings were turned into hard errors for Edition 2024 in [#13839](https://github.com/rust-lang/cargo/pull/13839). This left us with: