TL;DR: I'm proposing ability to set #[must_use] flag once for all functions in an entire crate/module/impl block.
"must use" behavior in Swift is close to what Rust does, except the default for functions is "must use". Instead of having an opt-in #[must_use], Swift has a @discardableResult annotation that opts out of the default "must use" behavior. I find Swift's default quite sensible and useful.
When I review my code for functions which could have #[must_use], I find that pretty much all of them could. There's a big gray area, but I find that "definitely discardable" results are a minority.
The different default also changes thinking about this feature. Instead of hypothetical "would anyone actually forget to use this value, and would that be a disaster?" to "is ignoring return value of this function part of its intended use?", which is more grounded in API design.
Adding #[must_use] everywhere is a bit of a chore and peppers code with annotations.
I realize Rust can't actually change the default without causing an annoying transition period, therefore I suggest adding an opt-in syntax that flips the #[must_use] default for entire scopes instead.
Or, in Rust's style, maybe this could be generalized one step more.
There's #[cfg_attr(cond, attr)] that sets attributes conditionally. Maybe there could be a similar attribute-setting attribute for automatically adding attributes?
Maybe:
#[default_attr(fn, must_use)]
mod everything_must_use {
…
}
I think the common worry here is warning noise. Like, yes, it's better to not call [T]::len if you don't need to, but did it really matter? The guidance for std seems to roughly be "it's only worth linting if you can point them at something else better instead".
I do think there's a place for a more nuanced lint that people might be more willing to turn on (since I don't see unused_results often). A not-quite-right starting point: tell me about unused results from things things where all the arguments are &impl Freeze but it's not a const fn.
Is the exclusion of const fn because those are already warned for? Or some other reason? (That the compiler theoretically could trivially optimize it out?)
Do note that any non-const fn can call into e.g. log::info! and have side-effects, even if the side-effects are not "purposeful" (i.e. the desired outcome of calling the fn and discarding the result).
It reminds me, for example, of a conversation in a PR I saw the other day about how clippy shouldn't complain about .unwrap_or(Vec::new()) (right now it suggests .unwrap_or_else(Vec::new)) because Vec::new() is const fn and so trivial that it's not worth mentioning the potentially-spurious creation of it any more than it would be to complain about .unwrap_or(1 << 15).
And, as I said, that was a "not-quite-right starting point". If I knew an amazing heuristic for this I'd have opened an issue already
Certainly. But that can also be things like calling a cache before you need it as a way of prefetching, where you don't want to remove the call but also don't want to use the result.
To me this isn't a concern. First, Swift has shown that default must_use works fine.
Second, the feature I'm proposing is not really about adding more warnings, but more about being able to have less syntax noise and boilerplate for existing use of #[must_use].
In other words, I want to add #[must_use] to 95% of functions I write. I'm doing that anyway, but current Rust syntax is tedious. My programming style is mainly side-effect-free, which means that majority of the time if I make a method that returns something, the whole point of calling that method is to use that value.
The fact that it’s const fn doesn’t have much to do with that, it’s only because it’s known to be so trivial that makes it ok. calculate_nth_prime(5678) could also be const fn but is a very non-trivial runtime call.
.unwrap_or(const { calculate_nth_prime(5678) }) is going to be interesting when that’s supported.