rust-lang / rust-lang/rust

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

Open
#159,078 2 comments 0 reactions 1 assignee View on GitHub

@Kokoro2336 is already working on this.

Since Jul 13, 2026.

A-lints A-visibility C-bug L-dead_code L-false-positive T-compiler T-lang
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

This basically raises https://github.com/rust-lang/rust/issues/74970#issuecomment-4409872603 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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.