Why can't rustc tell that a struct that implements a trait is unused?

The following code currently produces no warnings or errors:

#![deny(unused)]
fn main() {
    #[derive(Debug)]
    struct NotNeeded;
}

Playground Link

If the #[derive(Debug)] line is removed out, like so:

#![deny(unused)]
fn main() {
    struct NotNeeded;
}

then the compile fails with this error, as expected.

error: struct is never constructed: `NotNeeded`
 --> src/main.rs:3:12
  |
3 |     struct NotNeeded;
  |            ^^^^^^^^^
  |

It appears at first that rustc cannot tell that the struct is used if the struct implements a trait. But I tried the following and I get a compile error as expected:

#![deny(unused)]
fn main() {
    trait Foo {}
    struct NotNeeded;
    impl Foo for NotNeeded {}
}

Playground Link

So then I tried the following, which does not produce any errors or warnings:

#![deny(unused)]
fn main() {
    struct NotNeeded;
    impl Default for NotNeeded {
        fn default() -> NotNeeded {
            NotNeeded
        }
    }
}

Playground Link

These results leads me to think that the compiler cannot tell a struct is unused, if it implements an external trait.

Why is this the case? Is this an as yet undiscovered bug, a known limitation that is meant to be fixed eventually, or is there some reason that it has to be this way?

1 Like

I personally think this might be considered a bug, but I’m not sure. What I am sure about is that I need to correct your observations: the difference between Default and Foo in your example is not about external vs non-external traits but that Default contains a method that constructs a value of type NotNeeded if it is ever called (which it isn’t and that’s what the compiler seems to be missing).

Example:

fn main() {
    struct NotNeeded;
    trait Foo {
        fn bar() -> Self;
    }
    impl Foo for NotNeeded {
        fn bar() -> NotNeeded { NotNeeded }
    }
}

gives no warning.

Some rough ideas of mine follow after thinking about what might be going on for no more than a few minutes. Comparing the example above to

fn main() {
    struct NotNeeded;
    fn bar() -> NotNeeded { NotNeeded }
}

which does complain about both bar() and NotNeeded being unused, my conclusion is that Rust seems to never interpret trait methods as being unused.

I can make sense of this since especially for an external trait, you might never call a particular one (of many) trait methods on a private type, but you still need to define it to make your type instance that trait. In this case you wouldn’t want an unused warning. I guess some improvement could be made by changing the analysis to just not report a trait method being unused but still do the analysis so that the compiler could still conclusions such as, in particular, that a type is never constructed.

Additionally or alternatively I could imagine an analysis that creates a warning whenever a trait implementation on a private type is not actually needed/used anywhere, although that might be problematic, too, since then the compiler would give you warnings about unused Debug impl’s on your private types all the time, etc. But still, such an analysis might be an alternative to a potentially difficult method-by-method analysis, which allows the compiler, even if it doesn’t produce any warnings, to conclude that a type is actually unused if it is only constructed in an unused trait-impl.

3 Likes

This also produces no warnings:

fn main() {
    struct NotNeeded;
    trait Foo {
        fn bar() -> Self;
    }
    impl Foo for NotNeeded {
        fn bar() -> NotNeeded { todo!() }
    }
}

But I must still admit that you are correct that a trait does not need to be external to cause a lack of warnings, and that I had missed that. However, that doesn't explain why #[derive(Debug)] also caused a lack of warnings. If I implement Debug manually like so:

fn main() {
    struct NotNeeded;
    impl std::fmt::Debug for NotNeeded {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            f.debug_struct("NotNeeded")
             .finish()
        }
    }
}

I still don't get any warnings.

Interestingly, the following does produce a warning about NotNeeded being unused.

fn main() {
    struct NotNeeded;
    trait Foo {
        fn bar() -> u8;
    }
    impl Foo for NotNeeded {
        fn bar() -> u8 { 0 }
    }
}

So, at this point I think that no warnings are produced if a struct implements a trait where one of the methods takes as a parameter or returns an instance of the struct in question.