-
Notifications
You must be signed in to change notification settings - Fork 1.7k
RFC: Extend manifest dependencies with used
#3920
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
base: master
Are you sure you want to change the base?
Changes from all commits
a769f7f
93b717a
d6c64a3
fcc06ed
cba23b9
027b6c3
c4d9a80
c23e9a1
42547d8
6075111
5777fcc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,276 @@ | ||
| - Feature Name: `used-deps` | ||
| - Start Date: 2026-02-12 | ||
| - RFC PR: [rust-lang/rfcs#3920](https://github.com/rust-lang/rfcs/pull/3920) | ||
| - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
|
||
| ## Summary | ||
| [summary]: #summary | ||
|
|
||
| Extend Cargo's dependency specifications so users can mark that a dependency is used, independent of what [unused externs](https://doc.rust-lang.org/nightly/rustc/json.html#unused-dependency-notifications) says. | ||
|
|
||
| ## Motivation | ||
| [motivation]: #motivation | ||
|
|
||
| Rustc can report to Cargo | ||
| [unused externs](https://doc.rust-lang.org/nightly/rustc/json.html#unused-dependency-notifications) | ||
| which Cargo can aggregate and report back which dependencies are unused. | ||
|
|
||
| However, not all dependencies exist for using their `extern` in the current package, including | ||
| - activating a feature on a transitive dependency | ||
| - pinning the version of a transitive dependency in `Cargo.toml` (however, normally this would be done via `Cargo.lock` or `target."cfg(false)".dependencies`) | ||
|
|
||
| The user needs a way to be able to augment what the compiler reports with their intentions. | ||
|
|
||
| ## Guide-level explanation | ||
| [guide-level-explanation]: #guide-level-explanation | ||
|
|
||
| Say I have the following packages: | ||
| ```toml | ||
| [package] | ||
| name = "user" | ||
|
|
||
| [dependencies] | ||
| abstraction = "1.0" | ||
| mechanism = { version = "1.0", features = ["semantic-addition"] } | ||
| ``` | ||
|
|
||
| ```toml | ||
| [package] | ||
| name = "abstraction" | ||
|
|
||
| [dependencies] | ||
| mechanism = "1.0" | ||
| ``` | ||
|
|
||
| ```toml | ||
| [package] | ||
| name = "mechanism" | ||
|
|
||
| [features] | ||
| semantic-addition = [] | ||
| ``` | ||
|
|
||
| In this case, `user` references `abstraction` within its Rust source code. | ||
| The dependency on `mechanism` is only to activate the `semantic-addition` feature. | ||
|
|
||
| With [cargo#16600](https://github.com/rust-lang/cargo/pull/16600), | ||
| Cargo would report `mechanism` as an unused dependency: | ||
| ``` | ||
| [WARNING] unused dependency | ||
| --> Cargo.toml:6:1 | ||
| | | ||
| 6 | mechanism = { version = "1.0", features = ["semantic-addition"] } | ||
| | ^^^^^^^^^ | ||
| | | ||
| = [NOTE] `cargo::unused_dependencies` is set to `warn` by default | ||
| [HELP] remove the dependency | ||
| | | ||
| 6 - mechanism = { version = "1.0", features = ["semantic-addition"] } | ||
| | | ||
| [HELP] to keep the dependency, mark it as used: | ||
| | | ||
| 6 | mechanism = { version = "1.0", features = ["semantic-addition"], used = true } | ||
| | +++++++++++++ | ||
| | | ||
| ``` | ||
|
|
||
| To resolve this, `user` can: | ||
| ```toml | ||
| [package] | ||
| name = "user" | ||
|
|
||
| [dependencies] | ||
| abstraction = "1.0" | ||
| # For feature activation | ||
| mechanism = { version = "1.0", features = ["semantic-addition"], used = true } | ||
| ``` | ||
|
|
||
| Cargo would see that the dependency is marked as `used` and silence the lint. | ||
|
|
||
| ## Reference-level explanation | ||
| [reference-level-explanation]: #reference-level-explanation | ||
|
|
||
| Within [Specifying Dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html) | ||
|
|
||
| ### `used` | ||
|
|
||
| Always consider this dependency used | ||
|
|
||
| Applicability: | ||
| - `[*dependencies]`: yes | ||
| - `[workspace.dependencies]`: no | ||
| - `[patch]`: no | ||
|
|
||
| Type: `bool` | ||
|
|
||
| ## Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
|
||
| With any of the current options, | ||
| if the dependency ever becomes truly unused, | ||
| there is no way to report this to the user. | ||
|
|
||
| ## Rationale and alternatives | ||
| [rationale-and-alternatives]: #rationale-and-alternatives | ||
|
|
||
| As we are restricted to TOML syntax, we don't have a general purpose way of handling lint control like `#[expect]`. | ||
|
|
||
| ### `used` | ||
|
|
||
| Pros: | ||
| - Close to the dependency | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is that really such a benefit? When searching for how to modify the behavior of a lint, checking how is a lint being configured or why is a given dependency not being marked as unused, I wouldn't really think of going to the dependency table. Rather I would go look at the lint configuration. Similarly to when I configure e.g. disallowed functions ( The way I interpret the But configuring lints seems like a separate concern, that is also much more of an implementation detail of the given crate, and it IMO makes more sense for it to live in lint config, rather than here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is a benefit. CPU caches are driven by the assumption of locality, that the same they will access memory that is close together over a close together period of time. I think locality of code is also an important consideration for us as humans to read and understand code and data. The perfect abstraction means you don't need to look at a function implementation, only its call site. Perfect abstractions don't exist but they are on a gradient. You can get away with a lower quality abstraction when the code is closer together while further away, you need a higher quality abstraction because it is harder to process and reason about because of the lack of locality. I think the analogy with That said, this isn't the only consideration here. We are weighing multiple benefits and downsides. |
||
|
|
||
| Downsides: | ||
| - Manifest schema is being extended for control of a one off lint which can be incongruous with other lints and encourage the doing this for other lints which could overcomplicate manifest files | ||
|
|
||
| ### `used.reason` | ||
|
|
||
| If we added `reason`, it would be unused by Cargo and could just as well be a comment. | ||
| In contrast, Rust's `reason` can be applied to `warn`, `deny`, and `forbid` lints and reported back in the diagnostic | ||
| ([reference](https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-reasons)). | ||
|
|
||
| ### Lint config | ||
|
|
||
| ```toml | ||
| [lints.cargo] | ||
| unused_dependencies = { level = "warn", used = ["mechanism"] } | ||
| ``` | ||
|
|
||
| Pros: | ||
| - Follows a consistent pattern for where to configure lints | ||
|
|
||
| Downsides: | ||
| - Far away from the dependency specification, making it hard to reason about when looking at `mechanism` | ||
| - Need to decide what to do when `mechanism` becomes used or doesn't exist | ||
| - This config should not be inherited from `[workspace.lints]` | ||
| - Can't inherit `[workspace.lints]` *and* specify lint config in `[lints]` ([#13157](https://github.com/rust-lang/cargo/issues/13157)) | ||
| - Blunt hammer, not distinguishing which dependencies table this applies to | ||
| - Having it reference the dependencies table makes this more complicated and requires us to have a way to refer to `[dependencies]` | ||
|
|
||
| ### `_dep` | ||
|
|
||
| ```toml | ||
| _mechanism = { package = "mechanism", version = "1.0", features = ["semantic-addition"] } | ||
| ``` | ||
|
|
||
| Pros: | ||
| - Close to the dependency | ||
| - Aligns with Rust treating `_` as "don't care" | ||
| - Low overhead for design and implementation | ||
|
|
||
| Downsides: | ||
| - Doesn't mix well with kebab case names (e.g. `_regex-syntax`) | ||
| - The Cargo team was hesitant to officially support something similar for features ([#10794](https://github.com/rust-lang/cargo/issues/10794)) | ||
| - Affects sort order, moving it away from any related packages | ||
|
|
||
| ## Prior art | ||
| [prior-art]: #prior-art | ||
|
|
||
| ### Rust | ||
|
|
||
| ```rust | ||
| // Will warn | ||
| let something = foo(); | ||
|
|
||
| // Ways of resolving | ||
| let _ = foo(); | ||
| let _description = foo(); | ||
| #[expect(unused_variables, reason = "description")] | ||
| let something = foo(); | ||
|
|
||
| #[expect(unused_variables, reason = "description")] | ||
| mod { | ||
| fn bar() { | ||
| let something = foo(); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### `cargo udeps` | ||
|
|
||
| [Documentation](https://github.com/est31/cargo-udeps?tab=readme-ov-file#ignoring-some-of-the-dependencies) | ||
|
|
||
| ```toml | ||
| [package.metadata.cargo-udeps.ignore] | ||
| normal = ["if_chain"] | ||
| #development = [] | ||
| #build = [] | ||
|
|
||
| [dependencies] | ||
| if_chain = "1.0.0" # Used only in doc-tests, which `cargo-udeps` cannot check. | ||
| ``` | ||
|
|
||
| ### `cargo machete` | ||
|
|
||
| [Documentation](https://github.com/bnjbvr/cargo-machete?tab=readme-ov-file#false-positives) | ||
| ```toml | ||
| [dependencies] | ||
| prost = "0.10" # Used in code generated by build.rs output, which cargo-machete cannot check | ||
|
|
||
| # in an individual package Cargo.toml | ||
| [package.metadata.cargo-machete] | ||
| ignored = ["prost"] | ||
|
|
||
| # in a workspace Cargo.toml | ||
| [workspace.metadata.cargo-machete] | ||
| ignored = ["prost"] | ||
| ``` | ||
|
|
||
| ### `cargo shear` | ||
|
|
||
| [Documentation](https://github.com/Boshen/cargo-shear) | ||
| ```toml | ||
| [package.metadata.cargo-shear] | ||
| ignored = ["crate-name"] | ||
| ``` | ||
|
|
||
| ### `cfg(false)` dependencies | ||
|
|
||
| To specify a version requirement on a package without using it, you can do: | ||
| ```toml | ||
| [target."cfg(false)".dependencies] | ||
| foo = "1.0.0" | ||
| ``` | ||
|
|
||
| This would not work for feature activations. | ||
|
|
||
| ### `peerDependencies` | ||
|
|
||
| npm has the concept of | ||
| [`peerDependencies`](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#peerdependencies) | ||
| for requiring a version of a package if it exists without actually building against this. | ||
| This was developed for plugins to specify the version of what they plug into ([announcement](https://nodejs.org/en/blog/npm/peer-dependencies)). | ||
|
|
||
| ## Unresolved questions | ||
| [unresolved-questions]: #unresolved-questions | ||
|
|
||
| Is this the right direction? | ||
|
|
||
| ## Future possibilities | ||
| [future-possibilities]: #future-possibilities | ||
|
|
||
| ### Programmatic reason | ||
|
|
||
| The user being able to tell Cargo the reason in a way that it can verify it and still warn if the reason no longer applies. | ||
|
|
||
| For example, say we had: | ||
| ```toml | ||
| [package] | ||
| name = "user" | ||
|
|
||
| [dependencies] | ||
| abstraction = "1.0" | ||
| mechanism = { version = "1.0", features = ["semantic-addition"], used.transitive = true } | ||
| ``` | ||
| Then if `abstraction` is removed, we could start warning about `mechanism` again. | ||
|
|
||
| However for `transitive`, this could get complicated to have access to the dependency resolution graph at the time the warning is being handled. | ||
|
|
||
| ### Potential lints that may need configuration | ||
|
|
||
| - [#5340: Lint against non semver-open dependencies](https://github.com/rust-lang/cargo/issues/5340) | ||
| - [#9058: Warning when large binary files are included into the bundle](https://github.com/rust-lang/cargo/issues/9058) | ||
| - [#13681: Build script allowlist mode](https://github.com/rust-lang/cargo/issues/13681) | ||
| - [#15580: Lint for redundant feature names](https://github.com/rust-lang/cargo/issues/15580) | ||
| - [#15581: Lint for negative feature names](https://github.com/rust-lang/cargo/issues/15581) | ||
| - [#15590: Lint for feature named for private dependency](https://github.com/rust-lang/cargo/issues/15590) | ||
Uh oh!
There was an error while loading. Please reload this page.