-
Notifications
You must be signed in to change notification settings - Fork 1.7k
RFC: Inheriting of default-features in Cargo
#3945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
epage
wants to merge
9
commits into
rust-lang:master
Choose a base branch
from
epage:inherit-default
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+214
−0
Open
Changes from 4 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
25e92bb
Add skeleton
epage dffd307
feat: Initial draft
epage 2470445
chore: Rename file for RFC #
epage 0516299
chore: Update RFC# in text
epage 37f5751
style: Fix spelling
epage 46ec9bb
fix(summary): Inheriting deps, not packages
epage dbb4f28
style(motivation): Fix a typo
epage 896c5b0
style(motivation): Fix a typo
epage 51af31f
style(motivation): Fix a typo
epage File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| - Feature Name: `inherit-default-features`) | ||
| - Start Date: 2026-04-06 | ||
| - 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 | ||
| [summary]: #summary | ||
|
|
||
| Allow disabling default features locally when inhering a package. | ||
| ```toml | ||
| [workspace] | ||
|
|
||
| [workspace.dependencies] | ||
| serde = "1" | ||
| ``` | ||
| ```toml | ||
| [package] | ||
| name = "foo" | ||
|
|
||
| [dependencies] | ||
| serde = { workspace = true, default-features = false } | ||
| ``` | ||
|
|
||
| ## Motivation | ||
| [motivation]: #motivation | ||
|
|
||
| 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 <https://github.com/rust-lang/cargo/issues/8415#issuecomment-727245250> | ||
|
|
||
| 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 | ||
|
|
||
| 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 | ||
|
|
||
| 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: | ||
|
|
||
| - 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) | ||
|
|
||
| Inherited dependencies cannot use any other dependency key (such as version or default-features). | ||
|
|
||
| ## Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
|
||
| 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 | ||
|
|
||
| ### 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 | ||
|
|
||
| ## Unresolved questions | ||
| [unresolved-questions]: #unresolved-questions | ||
|
|
||
| ## Future possibilities | ||
| [future-possibilities]: #future-possibilities | ||
|
|
||
| Deprecate dependency feature inheritance, removing it in a future edition. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.