-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unactionable "field is never read" warning for printed structs with the Debug derive #88900
Comments
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 The same goes for Here's the relevant PR + a lot of discussion: #85200 |
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:
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. |
Nominating this for T-lang awareness, and possible discussion. |
@llogiq The piece of code that you link to reads the 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). |
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. |
That's what I was going to say. Nothing was changed with regards to the |
For those wondering about disruptiveness of the change, we had to add this to https://fuchsia-review.googlesource.com/c/fuchsia/+/581062 |
From the disruptiveness angle, I think it shouldn't matter how disruptive a warning change is, because that's the deal of Separate from this is the question whether the lint is a good idea. I lean towards yes. Some thoughts:
|
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 |
Effect of this lint on rust-analyzer: https://github.com/rust-analyzer/rust-analyzer/pull/10248/files There were 8 cases of this firing. 3 of them were due to field being needed by |
I want to take a closer look at the bevy issue (bevyengine/bevy#2811)! I want to understand why it can be considered a false warning -- to me, it seems like a case where the lint emits a true warning. More generally, I don't think that a mere fact of printing a struct with Bevy usage is from a literal example, so I'd like to start with a different, more "real world" case. The code could look roughly like this: #[derive(Debug)]
struct MyEvent {
pub message: String,
pub random_value: f32,
}
fn receiving_system(mut event_reader: EventReader<MyEvent>) {
for my_event in event_reader.iter() {
log::debug!("processing {:?}", *my_event);
do_work(my_event)
}
} Here, we have an event processing loop, which logs an event, and then processes it. In this case, I would argue that it makes sense to emit a warning if, eg What makes the actual bevy case different is that it's an example, so the That being said: I agree that there are cases where you do want to use |
Thoughts about blast radius: it's very clearly suboptimal if the rollout path for big consumers like Fuchsia is to just allow the warning on every call site (changeset link). Even if each one of those 93 cases is a true warning, and there's indeed a bunch of deadcode in Fuchsia, the experience should somehow be nicer. I think an ideal story would be something like this: John at MonorepoInc is tasked with upgrading rust compiler. The build after upgrade contains 128 new warnings. Upon examining a dozen of then, a cold sweat runs down John's spine, as every case does look like a potential issue. Nonetheless, John is able to proceed with upgrade by selectively allowing a new warning in the central build file. After the upgrade, John creates a series of tickets to fix the warning and re-enable it on per-subsystem basis. A week later, the warning is re-enabled globally, and the code a MonorepoInc contains no problematic code. The problem with this change is that it's not a new lint, its an extension to an existing one, so you can't selectively disable just the bit about |
This seems like the core, to me. |
As another data point that is slightly different: I have a use case where I have a bunch of structs that are only written to and then logged using their (derived) #[derive(Debug)]
struct S {
f: usize,
}
fn main() {
let s = S { f: 0 };
println!("{:?}", s);
} Would it be possible to make an exception for this case as the fields are actually read? |
Issue is being tracked in <rust-lang/rust#88900>. Basically the dead_code lint was changed to ignore derive fmt::Debug implementations, generally a good thing, but the metrics are only written to and then printed using the (derived) fmt::Debug implementations so they are actually read.
Issue is being tracked in <rust-lang/rust#88900>. Basically the dead_code lint was changed to ignore derive fmt::Debug implementations, generally a good thing, but the metrics are only written to and then printed using the (derived) fmt::Debug implementations so they are actually read.
Aren't the unused lints influencing each other? Right now, when I initialize an unused field of a struct with the result of a function, the function does not show up as unused. But often there is an avalanche of lints firing. E.g. when function foo calls bar, and foo is unused, then both foo and bar are shown as unused. So I'm not sure if splitting up the dead code lint group is a good strategy in general, although it might be helpful for the migration. I think additions of If you add a lint to support some migration, can you remove it after a while? IIRC the stability rules allow removal of lints, right? It has to land in stable because some users never use nightly and those should have the same level of comfort during a compiler upgrade. |
We ended up adding a comment above every Also, the true number of cases ended up being ~150. With regard to |
Side note that it is possible to put the #[derive(Debug)]
struct Foo {
#[allow(dead_code)]
x: u32
}
fn main() {
let _foo = Foo { x: 22 };
} I realize it's still work to add those lints, I wonder if we could make this a "cargo fix"'able thing. (I think this was suggested before, but I wanted to note it anyway :) |
A few more expansive thoughts here. There are two questions at play. First, was the decision to expand
This brings us to the second question: can we find less disruptive ways of rolling these lint changes out?
All that said, I'm not sure what the best fix is. One option might be having some version of cargo fix that can be used to "fixup" warnings. Another might've been converting the |
FWIW the crates.io codebase also ran into this issue (see rust-lang/crates.io#4030), though luckily only in a single case. It still feels strange to me to exchange false-negatives for false-positives though. This might be naive, but how hard would it be to disable the lint if we detect that the |
alternatively, if we want to keep it, it would be helpful for everyone that is surprised by this new behavior if the compiler added a hint like:
|
The dead code analysis is more strict now. In particular, fields are considered unused, even if they are used in derived code, like Debug derive. Cf. rust-lang/rust#88900
The dead code analysis is stricter now. In particular, fields are considered unused, even if they are used in derived code, like Debug derive. Cf. rust-lang/rust#88900
The dead code analysis is stricter now. In particular, fields are considered unused, even if they are used in derived code, like Debug derive. Cf. rust-lang/rust#88900
That is not correct for us either. Our use-case is that the debug output of some of these fields is purposely used within production applications:
It seems reasonable that this is a warning when the
I would agree with that. How about having a separate lint - close to dead code, for these kind of implementations? |
Even if this warning is reasonable, it would be a good idea if the message explained that derive(Debug) is ignored for purposes of the lint. As it happens, I spent a lot of time tearing my hair out trying to figure out why the warning was being triggered. (In my case I have structs that exist only to be dumped to a log using debug prints.) |
Might this also be the reason why rust 1.57.0 fails to build itself?
|
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. |
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. |
@lexi-brt that's what |
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. |
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
The current output is:
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).
The text was updated successfully, but these errors were encountered: