- Feature Name: none?
- Start Date: 2015-02-18
- RFC PR: rust-lang/rfcs#1940
- Rust Issue: rust-lang/rust#43302
Summary
Support the #[must_use] attribute on arbitrary functions, to make
the compiler lint when a call to such a function is ignored. Mark
PartialEq::{eq, ne} #[must_use] as well as PartialOrd::{lt, gt, le, ge}.
Motivation
The #[must_use] lint is extremely useful for ensuring that values
that are likely to be important are handled, even if by just
explicitly ignoring them with, e.g., let _ = ...;. This expresses
the programmers intention clearly, so that there is less confusion
about whether, for example, ignoring the possible error from a write
call is intentional or just an accidental oversight.
Rust has got a lot of mileage out connecting the #[must_use] lint to
specific types: types like Result, MutexGuard (any guard, in
general) and the lazy iterator adapters have narrow enough use cases
that the programmer usually wants to do something with them. These
types are marked #[must_use] and the compiler will print an error if
a semicolon ever throws away a value of that type:
fn returns_result() -> Result<(), ()> {
Ok(())
}
fn ignore_it() {
returns_result();
}
test.rs:6:5: 6:11 warning: unused result which must be used, #[warn(unused_must_use)] on by default
test.rs:6 returns_result();
^~~~~~~~~~~~~~~~~
One of the most important use-cases for this would be annotating PartialEq::{eq, ne} with #[must_use].
There’s a bug in Android where instead of modem_reset_flag = 0; the file affected has modem_reset_flag == 0;.
Rust does not do better in this case. If you wrote modem_reset_flag == false; the compiler would be perfectly happy and wouldn’t warn you. By marking PartialEq #[must_use] the compiler would complain about things like:
modem_reset_flag == false; //warning
modem_reset_flag = false; //ok
See further discussion in #1812.
Detailed design
If a semicolon discards the result of a function or method tagged with
#[must_use], the compiler will emit a lint message (under same lint
as #[must_use] on types). An optional message #[must_use = "..."]
will be printed, to provide the user with more guidance.
#[must_use]
fn foo() -> u8 { 0 }
struct Bar;
impl Bar {
#[must_use = "maybe you meant something else"]
fn baz(&self) -> Option<String> { None }
}
fn qux() {
foo(); // warning: unused result that must be used
Bar.baz(); // warning: unused result that must be used: maybe you meant something else
}
The primary motivation is to mark PartialEq functions as #[must_use]:
#[must_use = "the result of testing for equality should not be discarded"]
fn eq(&self, other: &Rhs) -> bool;
The same thing for ne, and also lt, gt, ge, le in PartialOrd. There is no reason to discard the results of those operations. This means the impls of these functions are not changed, it still issues a warning even for a custom impl.
Drawbacks
This adds a little more complexity to the #[must_use] system, and
may be misused by library authors (but then, many features may be
misused).
The rule stated doesn’t cover every instance where a #[must_use]
function is ignored, e.g. (foo()); and { ...; foo() }; will not be
picked up, even though it is passing the result through a piece of
no-op syntax. This could be tweaked. Notably, the type-based rule doesn’t
have this problem, since that sort of “passing-through” causes the
outer piece of syntax to be of the #[must_use] type, and so is
considered for the lint itself.
Marking functions #[must_use] is a breaking change in certain cases,
e.g. if someone is ignoring their result and has the relevant lint (or
warnings in general) set to be an error. This is a general problem of
improving/expanding lints.
Alternatives
-
Adjust the rule to propagate
#[must_used]ness through parentheses and blocks, so that(foo());,{ foo() };and evenif cond { foo() } else { 0 };are linted. -
Should we let particular
impls of a function have this attribute? Current design allows you to attach it inside the declaration of the trait.
Unresolved questions
- Should this be feature gated?