Here's a demo program that illustrates my question; there may be simpler examples, but I'm still wrapping my head arouns rust, so this will have to do for now. Without the "allow(dead code]" directive, I get a compiler warning: "warning: field x is never read" at line 4. It seems to me that the println!() call would count for reading every element of the Answer struct. If I knew why it doesn't I think I might understand Rust better .
I'm running rustc 1.78.0 (9b00956e5 2024-04-29) on MacOs 14.5
demo.rs:
#[allow(dead_code)]
#[derive(Debug)]
struct Answer {
x:i32,
}
fn main() {
let x = 42;
let ans = Answer{x};
println!("The answer is {:?}", ans);
}
If you use cargo check to compile this code, you'll get a note under the warning:
= note: `Answer` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
So yes, using Debug implementation doesn't count as use, since it's mostly used for, well, debugging, not for any business-logic or user-facing output.
Not just “mostly”. Debugshould not be used for user-facing output, because it is not guaranteed to do what you are hoping for.
Derived Debug formats are not stable, and so may change with future Rust versions. Additionally, Debug implementations of types provided by the standard library (std, core, alloc, etc.) are not stable, and may also change with future Rust versions.
There's even a PR in progress which will add a compilation option which causes all derived Debug implementations and {:?} formats to print nothing, in order to remove the code-size overhead of the debug formatting operations, and to exercise the above non-guarantee.
The further rationale for excluding Debug implementations is that most types should implement Debug, and if Debug (and Clone) were not excluded, then it'd be impossible to ever get any warnings of unused struct fields.
Heh. I see this was realized, but the Termination implementation for Result relies on Debug. That was a mistake/misdesign in the first place, but is still what we got, with the outcome that Debug implementations consciously used with the trait are meant for user-facing outputs. E.g.: anyhow.
Indeed it's quite unfortunate, but individual programs can always write their main()s to not rely on the implicit printing, either by printing explicitly and then returning () or ExitCode, or by writing Termination implementations of their own. A library can never require the program to return a specific thing from main().
The trait exists in large part to allow a distinction between errors and panics (bugs) in main, by enabling ? in main -- and getting rid of unwrap in main.
This example demonstrates the ideal better than I can type up right now.
That shows how you want useful to the user Display implementations for your errors, right? So if you currently have such nice user-facing errors getting displayed by Termination, that's a manual Debug impl (that forwards to Display, presumably) and wouldn't be affected by that PR.
I was saying that the (derived) Debug output getting displayed by Termination is only as user facing as a panic is - in neither case is that actually intended for a user, so I'm pretty fine both outputs getting nuked in a context where you consider binary size more critical than debugability in the field.
I get your point now. (But the PR is still effected because people derive Debug and then use the derived Debug elsewhere. I'd have to dig to see how prevalent this is in the ecosystem.)
I should also note, how user-friendly impl Result<_, _> for Termination was intended to be was more contentious than I remembered. But I'll save any more comments about that for the IRLO thread.
Honestly, for all the technical discussion above, this really shows up one of Rust's greatest weaknesses -- the Rust language lacks when it comes to simple, common sense logic. As a Rust learner, I've just had to put up with this very same problem, even if it doesn't make sense. Really, setting aside how Debug works or doesn't work, if you create a variable and then use it in a println!() statement, you should not get a warning that your variable hasn't been used. The Rust developers need to come down from their intellectually lofty pedestals and consider how the rest of us use the language. Straightforward simplicity should be one of their guiding principles.
As for this particular warning, I first have to waste my time trying to make sure I don't have a real (rather than compiler-imagined) mistake, and then ignore it. I shouldn't have to do that.
You have a valid argument since this can be confusing, and what you want here is something others may want as well. But there is another valid argument, which is that some others (including me) would like to get a warning when something is unused when compiled with --release, since this indicates a possible bug. So there is a tradeoff. Perhaps Rust could have a way to meet both needs, but that might add more complexity which (as you point out) is undesirable. From my point of view, avoiding bugs is the more important thing, but that is just one opinion.