Why is ignoring a `must_use` trigger a warning rather than an error?


#1

One of Rust’s core benefits seems to be its error-handling strategy; Joe Duffy, for instance, mentions several times throughout his post on error-handling in Midori that Rust appears to support exactly the sort of error model he’s promoting. And one of the best things about Rust’s error-handling is that it makes error handling difficult to avoid.

But my experience in unsafe languages is that developers often treat warnings as “warts”–ugly messages that simply aren’t important enough to address consistently. Without -Werror, C and C++ projects can quickly become full of warnings that developers rarely even bother to read.

So in the case of the #[must_use] attribute (and especially its application to the Result type), the author of a function is attempting to demand that a particular scenario be handled or at least explicitly ignored, but a programmer who calls that function can in fact get away without even noticing that they’ve done anything wrong (in the case where their code already produces lots of warnings that are ignored).

I realize that ignoring warnings–and especially ignoring lots of warnings–probably isn’t considered “good practice” in Rust. But it’s not “good practice” in C or C++, either, which just shows how far “good practice” gets you.

Is there a reason for making this attribute trigger a warning rather than an error? Was there internal discussion about how the attribute should behave?


#2

I also think it would’ve been better for that to be an error by default, but in my experience so far, I have not come across many Rust projects where people ignore warnings out of laziness—especially that one.


#3

I’ve seen the idea that rust’s default lints should as far as possible not have false positives. That’s something that increases the perceived value of lints and may help us here to have more users take care to make sure there are no warnings.


#4

You can always deny warnings.

Besides, I’d prefer making potential bad practices (proportionally) hard, but not forbidden, and leave the choice to developers trusting them to know better. Warning here seems appropriate.


#5

No, I just want ro return something more detailed than just “None”. I think current realization is absolutely fine.


#6

A downside with errors is that it makes refactoring/experimentation harder: the compiler is requiring that code is perfect before the developer can even run their binary on their own computer, under their own watchful eye (where errors etc are less likely to happen, and are probably non-critical). For circumstances like that, people are pushed towards turning off the lint entirely, adding #[allow(must_use)] to their code, rather than just temporarily ignoring the warnings until they’ve completed their main goal.

As others have said, Rust seems to have encouraged a culture of not just ignoring all warnings, and, if desired, it is easy to convert all warnings to errors by simply adding #![deny(warnings)] to the top of a crate (and the compiler/cargo are designed in a way that learns from the lessons of -Werror in C/C++).


#7

How could this be a false positive? I suppose you could have a function that’s known to never fail, but in that case errors would be better handled with a panic.


#8

If all you want is detailed return-value information that can be safely ignored, variadic types that are not marked must-use seem more appropriate. Why would something be marked “must-use” be safely ignorable?


#9

That makes sense–in C++ it can definitely be frustrating to have a compile fail for an “uninteresting” reason and delay your ability to test your new code until you’ve fixed the issue (especially when you know that in the particular case the warning is a false positive). But the issue with using warnings is that once a target is built, incremental builds will never rebuild it even if it was built with warnings unless the target is then deleted (e.g. by a clean build) or the source code changes. I know Rust doesn’t have much support for incremental builds yet, though, so maybe this isn’t an issue at the moment. Are files that were built with warnings always recompiled by cargo?


#10

I don’t know if the interaction of incremental compilation and warnings have been planned in detail. It seems to me that the compiler could easily save warnings and re-emit them on each compilation when that code is unchanged.


#11

That would be wonderful. I asked about doing something like this for C++ with Clang and Ninja and essentially got the response “why not use -Werror”, which is what I ultimately ended up doing (despite the fact that someone also provided some Bash/CMake code to re-print warnings that might actually work–I just decided it was simpler to use -Werror than integrate that code into my existing build set-up).


#12

Example of a something that never fails yet returns Result:

use std::io::Write;

let mut buf=[0u8;16];
let str=b"any length str";
(&mut buf[..]).write(str);

#13

Right, in that particular context the programmer believes (correctly, I think) that the function call cannot fail. So why not use something like “try!” to invoke it?


#14

I’m confused. try! can only be used in a function that returns a Result, making this totally bogus error even more infectious.

I tend to do something like .expect("writing to an in-memory buffer cannot fail"), so if I’m wrong I get a clear error message. Other people do let _ =, which avoids the branch that the expect introduces. Other people may do #[allow(unused_must_use)], to avoid the branch and the boilerplate. All three seem like valid choices for a user to make.


#15

Sorry, I meant something that would check the return value and panic if it was an error. So your “expect” suggestion is exactly what I meant to suggest.

I don’t mean to say that I’d limit the choices a user could make; just that the default surprised me.


#16

I guess what you mean is that you think it should be set to deny(unused_must_use) by default? (When you said error, initially I thought you meant some sort of type error). I don’t have a strong opinion about the setting for this lint, but it would be backwards incompatible to upgrade it to deny at this point. I also don’t know if there are any lints set to deny by default at all right now.


#17

Yes, that’s what I meant, and I realize that it’s probably too late to change now. I was asking more from a language-design perspective.


#18

not really, afaik it would be legal to change the warn-by-default lint to a deny-by-default, since that would not break any cargo dependencies, but only the actually compiled crate, if it didn’t opt out with allow(must_use) or warn(must_use).


#19

It was an argument for why rust users might already take lints seriously even if they are “just warnings”, nothing more than that.