← Back to index
PR #3920Work-in-progress preview from an open pull request.View on GitHub ↗
REVIEW
#3920

used-deps

Authorepage
CreatedFeb 12 2026
UpdatedApr 23 2026
Rust Issue

Summary

Extend Cargo's dependency specifications so users can mark that a dependency is used, independent of what unused externs says.

Motivation

Rustc can report to Cargo unused externs which Cargo can aggregate and report back which dependencies are unused.

However, not all dependencies always use the 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)
  • dependencies that are only used based on RUSTFLAGS/build.rs (example)

The user needs a way to be able to augment what the compiler reports with their intentions.

Guide-level explanation

Say I have the following packages:

[package]
name = "user"

[dependencies]
abstraction = "1.0"
mechanism = { version = "1.0", features = ["semantic-addition"] }
[package]
name = "abstraction"

[dependencies]
mechanism = "1.0"
[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, 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:

[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

Within Specifying Dependencies

used

Always consider this dependency used

Applicability:

  • [*dependencies]: yes
  • [workspace.dependencies]: no
  • [patch]: no

Type: bool

Drawbacks

With any of the current options, if the dependency ever becomes truly unused, there is no way to report this to the user.

This doesn't scale to other potential dependency lints, including

  • wildcard dependencies (#4377)
  • non-semver version upper bound (#5340)
  • Version req is lower than Cargo.lock (#15583)
  • dependency isn't inherited (#15578)
  • Version requirement without fully specified lower bound (#15577)
  • unused public = true
  • implicit version requirement

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

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).

Lint config

[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)
  • 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

_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)
  • Affects sort order, moving it away from any related packages

Link control

mechanism = { version = "1.0", lib = false, features = ["semantic-addition"] }

Artifact dependencies has a lib field to control whether a package links to that dependency.

We could stabilize that piece independent of the rest of artifact dependencies and shift the focus of this lint to unused_lib_dependencies. We could suggest to users to remove the dependency or set lib = false.

Open questions:

  • How would artifact / unlinked dependencies resolve features? Independent of the rest of unified?
  • Does this cover all false-positive cases?

Pros:

  • Close to the dependency
  • Low overhead for design and implementation
  • Won't build if it goes stale, avoiding slowing down builds

Downsides:

  • Requires a comment to make sense of it
  • Does not help with dynamically used dependencies like with curl

As an aside: is lib or link a more appropriate field name?

Target dependencies

[target.'cfg(resolve = "versions")'.dependencies]
# replaces `cfg(false)` which may break with https://github.com/rust-lang/cargo/issues/6179
[target.'cfg(resolve = "features")'.dependencies]
mechanism = { version = "1.0", features = ["semantic-addition"] }

Add Cargo-specific built-in cfgs for what parts of Cargo should respect or ignore the dependencies.

resolve = "versions"

  • runs during version selection
  • ignored for feature selection
  • ignored for build

resolve = "features" is a superset of versions (ie version selection sets --cfg resolve=versions --cfg resolve=features)

  • runs during version selection
  • runs during feature selection
  • ignored for build

Pros:

  • Close to the dependency
  • Builds off of existing concepts in a way that can also scale to more use cases
  • resolve = "versions" can work without an MSRV bump

Downsides:

  • Carves into user cfgs, possibly breaking someone
  • Could lead to confusing error messages if a user gets clever and does not(resolve = "versions")
  • Each early stage in the process needs to know about later stages to correctly set --cfg
  • Does not help with dynamically used dependencies like with curl

Implicitly ignore duplicate dependencies

If a direct dependency is also a transitive dependency, Cargo can ignore it, assuming the user did it intentionally for dependency resolution or feature activation. False negatives is unlikely to have much of an impact because the dependency is already going to be built. The worst that can happen is some extra features get activated.

Pros:

  • Automatic

Downsides

  • Can lead to extra features being activated than needed
  • Does not help with dynamically used dependencies like with curl

Do nothing, have people disable the lint

This is likely insufficient on its own but may be a worthwhile answer for cases not as easily covered, like curl.

Prior art

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

[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

[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

[package.metadata.cargo-shear]
ignored = ["crate-name"]

cfg(false) dependencies

To specify a version requirement on a package without using it, you can do:

[target."cfg(false)".dependencies]
foo = "1.0.0"

This works for version constraints but not feature activations.

peerDependencies

npm has the concept of 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).

Unresolved questions

Is this the right direction?

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:

[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