bluenote10
bluenote10

Reputation: 26710

Why does Rust stop detecting dead structs if they implement/derive some trait?

Under normal circumstances, Rust warns if a struct is dead by generating a warning like:

struct `Foo` is never constructed

This is nice to detect unused types that can be removed.

However, I noticed that the compiler stops warning about dead structs as soon as they implement or derive some trait. For instance, for a source file consisting purely of:

struct DeadStruct1;

#[derive(Clone)]
struct DeadStruct2;

The compiler will only detect that DeadStruct1 is unused, but not DeadStruct2, which is obviously not used in this program either (example on Rust Playground):

warning: struct `DeadStruct1` is never constructed
 --> src/lib.rs:1:8
  |
1 | struct DeadStruct1;
  |        ^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

I'm wondering why this lint depends on whether or not a struct implements a trait?

Upvotes: 9

Views: 543

Answers (1)

FZs
FZs

Reputation: 18619

It is because of a known bug, it is discussed here and here.

But... it's weird.

I tested the January 1st builds of each year between 2018 and 2023, and the problem got worse over time.

  • The problem you're mentioning (with #[derive(Clone)], and also for PartialEq, Default; but not for Debug and Hash, and also not if the trait was manually implemented) had already been there in 2018-01-01. But at that point, only derived implementations caused it, manual implementations still had the warning.

  • In 2019-01-01, nothing seems to have changed.

  • But then, by 2020-01-01, some other cases stopped producing the warning too. This code demonstrates the behavior (from this post, also in the discussion linked above):

    struct Foo;
    
    // Does not prevent the warning
    trait Bar0 {}
    impl Bar0 for Foo {}
    
    // Does not prevent the warning
    trait Bar1 { fn dummy(); }
    impl Bar1 for Foo { fn dummy () {} }
    
    // Prevents the warning
    trait Bar2 { fn dummy(&self); }
    impl Bar2 for Foo { fn dummy (&self) {} }
    
    // Prevents the warning
    trait Bar3 { fn dummy() -> Self; }
    impl Bar3 for Foo { fn dummy () -> Self { todo!() } }
    
    // Prevents the warning
    trait Bar4 { type Dummy; }
    impl Bar4 for Foo { type Dummy = Self; }
    
    // Prevents the warning
    trait Bar5 { const DUMMY: Self; }
    impl Bar5 for Foo { const DUMMY: Self = Self; }
    
  • And, as we arrive to 2021-01-01, now any (non-blanket) trait implementation silences the warning, no matter if derived or manual, mentions Self or not!

    And this seems to be the behavior we have to this day.

Upvotes: 7

Related Questions