Skip to content

The dead_code_pub_in_binary lint wrongly triggers on public library items when --all-targets is used #159078

Description

@PaulDance

This basically raises #74970 (comment) to a dedicated discussion as the feature has landed in stable since.

Reproducing

  1. Generate a new empty library crate in a blank directory: cargo init --name tmp --lib --vcs=none.

  2. Add the lint to the crate's manifest file, thus giving:

    [package]
    name = "tmp"
    version = "0.1.0"
    edition = "2024"
    
    [lints]
    rust.dead_code_pub_in_binary = "warn"
  3. Add a public item to src/lib.rs and leave it unused in its tests, giving for example:

    pub fn add(left: u64, right: u64) -> u64 {
        left + right
    }
    
    pub fn hello() {}
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn it_works() {
            let result = add(2, 2);
            assert_eq!(result, 4);
        }
    }
  4. Run cargo check --all-targets.

Observed behavior

With this, the following output is emitted:

warning: function `hello` is never used
 --> src/lib.rs:5:8
  |
5 | pub fn hello() {}
  |        ^^^^^
  |
  = note: in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused
  = note: requested on the command line with `-W dead-code-pub-in-binary`

warning: `tmp` (lib test) generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s

This seems caused by the fact that the crate is compiled for the test target that is a form of binary and indeed does not use the new public item. While that can make sense at a low level, at a higher level it just seems like an undesirable side effect. In practice, it renders the lint pretty much unusable in large workspaces because the detected items are still part of the library's API and not part of a binary from the point of view of the crate itself, conceptually. This is particularly true for very small crates where test coverage would not make sense, for example with ones that only define constants to be shared by other crates in the same workspace. This concretely leads to a lot of triggers in our workspace.

Expected behavior

The stricter goal of the lint seems to me to detect unused items in "truly binary" targets, i.e. stuff residing in src/main.rs and such, to put it concretely. The lint should therefore not trigger for the cases described above. In the given example, that means both cargo check and cargo check --all-targets should not emit any warning.

Keeping --all-targets as part of the command used for e.g. CI is definitely desirable as it extends the lint coverage to everything at once, i.e. the command is always up-to-date. Being more specific with respect to the lint would require either enabling the lint only for a selected list of crates or somehow disabling it for others somehow. However, in both cases that would require too much maintenance when multiple people contribute to a large workspace: it would be easy for a new binary crate to be added without the lint enabled and passing by unnoticed during review. Restricting the lint to keep that flag while only detecting the interesting cases would therefore avoid these issues.

One could also argue that an exposed function should be covered by at least some test, which would make the lint not trigger for it anymore. However, that seems like it would much better reside in another lint rather than coupling the two use cases together because the concerns differ. Furthermore, some of the items would not (or almost never) make sense to cover with tests, for example constants, types, traits, etc., because in most cases, compilation is enough as a form of testing these items.

Public items in targets other than bin ones should still trigger the lint if unused as well. Indeed, they are also binary targets, i.e. things that cannot be imported from another crate, so linting for unused "public" items in these would definitely still make sense. For example, if a examples/whatever.rs or a tests/hello.rs has an public item that is not used in itself, then the lint should trigger for it.

Meta

rustc --version --verbose:

rustc 1.97.0 (2d8144b78 2026-07-07)
binary: rustc
commit-hash: 2d8144b7880597b6e6d3dfd63a9a9efae3f533d3
commit-date: 2026-07-07
host: x86_64-unknown-linux-gnu
release: 1.97.0
LLVM version: 22.1.6

This is also the case under nightly, specifically:

rustc 1.99.0-nightly (af3d95584 2026-07-09)
binary: rustc
commit-hash: af3d95584dbddcae597890340995509a7fb47a50
commit-date: 2026-07-09
host: x86_64-unknown-linux-gnu
release: 1.99.0-nightly
LLVM version: 22.1.8

@rustbot label A-lints A-visibility L-dead_code L-false-positive T-lang T-compiler

Metadata

Metadata

Assignees

Labels

A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.A-visibilityArea: Visibility / privacyC-bugCategory: This is a bug.L-dead_codeLint: dead_codeL-false-positiveLint: False positive (should not have fired).T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions