Skip to content

Unactionable "field is never read" warning for printed structs with the Debug derive #88900

Description

@cart

The latest nightly (rustc 1.57.0-nightly (8c2b6ea 2021-09-11)) introduces a new warning that I consider to be invalid.

Given the following code: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=14d790d2f286f58543c42d915f60e182

#[derive(Debug)]
struct Foo {
    a: String,
}

fn main() {
  let foo = Foo {
      a: "hello".to_string(),
  };
  
  println!("{:?}", foo);
}

The current output is:

warning: field is never read: `a`
 --> src/main.rs:3:5
  |
3 |     a: String,
  |     ^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: `playground` (bin "playground") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 1.56s

This feels very wrong to me. It means any struct created with the intent to be printed is "invalid" according to the compiler. This warning is not actionable. My only option is to suppress it, live with it, or insert some sort of "dummy read". This has broken our CI builds for Bevy (because we treat warnings as errors).

Activity

  1. added
    A-diagnosticsArea: Messages for errors, warnings, and lints
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Sep 12, 2021
  2. hellow554 commented on Sep 13, 2021

    @hellow554
    Contributor

    First off: Denying warnings is considered harmful [0] [1] [2] [3] and should not be default in a project. You may use it for testing purposes, but not actively as a blocker.

    Second: I can see, that you might think that the lint is wrong, but it is actually right. You never read the field a.
    It is used in the Debug output, yes, but not actively in your code.
    For the warning to silence you can prepend a underscore (e.g. _a) but that's it.

    The same goes for Clone btw.

    Here's the relevant PR + a lot of discussion: #85200

  3. cart commented on Sep 13, 2021

    @cart
    Author
    1. We don't hard-code deny(warnings), we pass in -D warnings as a rustc flag as part of our CI, which doesn't suffer from the "forward compatibility" issues called out in the four links you shared. I think most people would agree that warnings should be either resolved or suppressed, especially in public libraries.
    2. This doesn't change the fact that something uses it. If I were to implement a new Debug trait in a new crate (with an equivalent proc macro derive), then use that crate and derive that version of Debug on my Foo type, I wouldn't get the warning. This isn't about whether or not I directly touch the field. Its about whether something uses the field. As discussed in Ignore derived Clone and Debug implementations during dead code analysis #85200, the fact that Debug doesn't count is a new exception to a rule that applies to literally everything else.

    It sounds like ignoring Debug impls was a pragmatic choice to make this lint more useful. Debug is a common / recommended derive for structs, which meant that most structs weren't getting "unused field" lints. Working around this seems reasonable.

    However I think it is worth calling out that this specific case is relatively common / many people would consider it "valid". At the very least, I imagine some users would be confused, especially given that this isn't consistent with other traits and printing debug derives is super common. Warnings should be actionable. It is the compiler telling the user that something should be fixed / isn't recommended. In my ideal world either:

    1. The code above is considered valid by rustc: when a Debug impl is actually used via something like println, unused field lints are suppressed (because the fields are actually used by code the user chose to write).
    2. The code above is considered invalid by rustc, but equivalent tools are recommended / provided: if using Debug in this context is incorrect (ex: Display should be used instead), ideally there is a Debug-like derive for Display, and ideally rustc recommends that in these cases.

    Of course both of those solutions are complicated / controversial and I'm sure this was supposed to be an easy win. I'm not demanding any action here / this isn't a high priority for me. I'm just reporting a confusing / unintuitive new behavior. I doubt I'll be the last person to hit this.

  4. llogiq commented on Sep 13, 2021

    @llogiq
    Contributor

    We have another datapoint at synth. In our case, the code in question is not derived; the type_name field is actually read in a trait impl (though to be fair it's in an async fn, so there may be expansion involved, too).

  5. added
    T-langRelevant to the language team
    and removed
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Sep 13, 2021
  6. Mark-Simulacrum commented on Sep 13, 2021

    @Mark-Simulacrum
    Member

    Nominating this for T-lang awareness, and possible discussion.

  7. FabianWolff commented on Sep 13, 2021

    @FabianWolff
    Contributor

    We have another datapoint at synth. In our case, the code in question is not derived; the type_name field is actually read in a trait impl (though to be fair it's in an async fn, so there may be expansion involved, too).

    @llogiq The piece of code that you link to reads the data_type field, not the type_name field, so the latter may very well be unused.

    In general, only automatically derived impls should be ignored; if you find an exception to this, it's a bug (whereas ignoring derived implementations is intended).

  8. cynecx commented on Sep 14, 2021

    @cynecx
    Contributor

    I’ve been getting these warnings as well. The struct actually contains a Child (tokio::process::Child) handle. So at drop time of the struct the Child handle drops and eventually cleanups the child process. So it’s not technically true that the field is never read, because the drop glue certainly does.

    Edit: On second thought, this warning might have been lurking there all along because the struct has a Debug derive.

  9. FabianWolff commented on Sep 14, 2021

    @FabianWolff
    Contributor

    Edit: On second thought, this warning might have been lurking there all along because the struct has a Debug derive.

    That's what I was going to say. Nothing was changed with regards to the Drop trait, so the behavior you're complaining about must have been there before (it was just hidden by the Debug derive).

  10. tmandry commented on Sep 14, 2021

    @tmandry
    Member

    For those wondering about disruptiveness of the change, we had to add this to 93 153 fields in the main Fuchsia repo. I'd say this is among the most disruptive warnings changes we've had to fix. Most of these structs seem to only derive Debug; I haven't dug into what they're used for or whether these fields should exist yet.

    https://fuchsia-review.googlesource.com/c/fuchsia/+/581062
    https://fuchsia-review.googlesource.com/c/fuchsia/+/581066

  11. est31 commented on Sep 15, 2021

    @est31
    Member

    From the disruptiveness angle, I think it shouldn't matter how disruptive a warning change is, because that's the deal of -D warnings or #![deny(warnings)]: By enabling them, you get the duty of having to fix newly occurring warnings. I'm not going as far as @hellow554 that you shouldn't enable them, but that if you do, you lose your right to complain about breakage. If you don't like having to fix new warnings, that's your choice, no need to add -D warnings.

    Separate from this is the question whether the lint is a good idea. I lean towards yes. Some thoughts:

    • ➕ From the practical angle, it has been lauded to have found many unused fields in the compiler.
    • ➕ From a theoretical angle, the purpose of Debug derives is to aid printf debugging. Their purpose is not to extract data from a structure in an automatted way, by say parsing its output, so there won't be proper "use" of a field somewhere in the code base even if you debug-print it.
    • ➖ However, one can make a case that printf debugging is a usage of its own, and even if the debug impl is never used, it might be one day.
    • ➕ On the other hand, when I add a private function to print some internal state, but don't use it because I'm not debugging right now, then the compiler has no idea it's actually meant for debugging purposes so justifiably warns about it. That's what silencing the lint is for.
    • ➕ It's recommended to implement Debug in the API guidelines in both C-COMMON-TRAITS and in C-DEBUG. So you'll have lots of structs that implement the trait. If all members of these structs enjoy immunity from the "field is never read warning", won't that severely weaken the power of the lint?
    • This is more a consistency question, Feeding a variable to the dbg!() macro counts as use of the variable. People often create variables for the sole purpose of printf debugging, should they be reminded by warnings that those variables should go? Why treat fields differently?
  12. IceSentry commented on Sep 15, 2021

    @IceSentry
    Contributor

    I agree that just deriving Debug shouldn't hide unused warnings and making that change is nice. The issue is that if you actually use the trait for printing, it should count as using the value. Adding an #[allow(unused)] on a property that is both assigned and then printed using Debug doesn't feel like the right solution to this.

  13. 25 remaining items

  14. peterjoel commented on Jan 27, 2022

    @peterjoel
    Contributor

    A very common case (from looking at these errors in my codebase) is informational fields on error types. These are not really intended to be used as such but will show when the error is logged.

  15. lexi-brt commented on Sep 28, 2022

    @lexi-brt

    I think a better compromise would be to add a new Printable trait to replace debug. Printable would essentially be intended for structs who's sole purpose is to be printed. Of course, you would lose the "unused" warnings on any struct that derived from Printable.

  16. hellow554 commented on Sep 29, 2022

    @hellow554
    Contributor

    @lexi-brt that's what Display is for.

  17. d-sauter commented on Nov 30, 2022

    @d-sauter

    Another might've been converting the dead_code lint into a group, so that people could readily add #![allow(dead_code_in_derived_impls)] to their file or something like that (presumably temporarily).

    Was something like this ever added? Seems weird that rustc_trivial_field_reads is internal unstable. It doesn't make sense to lock this down, I have a use-case where the derive macro will generate reads and writes for specific fields. But I want to ensure the user of this macro still uses the field in their code.

  18. added
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Dec 11, 2024
  19. added
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    and removed
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    on Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-diagnosticsArea: Messages for errors, warnings, and lintsA-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.L-dead_codeLint: dead_codeT-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