I recently answered a Stack Overflow question where the poster wrote this bit of erroneous code:
let mut enter = String::new(); stdin().read_line(&mut enter).expect(r#"invalid key"#); enter.trim().to_lowercase();
They expected it to convert
enter to lowercase and couldn't figure out why their program didn't work. When I saw this I thought they must have ignored a warning, but no, it turns out
to_lowercase has neither a
#[must_use] attribute nor an associated Clippy warning.
This led me down a rabbit hole investigating the standard library's usage of the
#[must_use] attribute. Overall, I'd say the usage is inconsistent. It's used a lot but also omitted a lot. The guidelines on when to add
#[must_use] in the standard library don't set a clear policy on whether it should be used a lot or a little.
What do you think: Should it be used liberally on any function that has no use except for returning a value? Or should it be reserved only for error-prone functions that novices are likely to misuse?
As I see it, it could be helpful on any function that (a) has no mutable args; (b) has a non-unit return type; and (c) has no side effects. Side effects include things like mutating statics, spawning threads, and asserting preconditions.
These are essentially the rules the
must_use_candidate lint uses, so... I ran Clippy on the standard library. It turned up nearly 800 matches. Yowza!
Adding that many annotations would be a lot of churn so I wanted to poll the community and see what you all thought should be done (if anything). I pinged some library team folks and they were on board with discussing this here. I'm willing to get into the weeds and work on PRs if there is general consensus.
Also, it's important to note that while it might be annoying to be on the receiving end of new
#[must_use] warnings, adding them wouldn't break any stability guarantees.
To help guide the discussion I've extracted a set of examples and grouped them into rough categories.
Like the monsters that pose as treasure chests in Dark Souls and eat you when you get near, these are newbie traps. They sound like they might mutate their input argument but actually return a new value.
str::to_uppercase(self) -> String; str::to_lowercase(self) -> String; str::trim_left(&self) -> &str; str::trim_right(&self) -> &str; <[u8]>::to_ascii_uppercase(&self) -> Vec<u8>; <[u8]>::to_ascii_lowercase(&self) -> Vec<u8>; i32::swap_bytes(self) -> i32; i32::rotate_left(self, n) -> i32; i32::to_be(self) -> i32; i32::to_le(self) -> i32;
These methods also transform their input, but unlike the "mimics" above they don't trick anybody into thinking they might do it in place.
Option::copied(self) -> Option<T>; Option::cloned(self) -> Option<T>; Rc::into_raw(self) -> *const T; Box::<str>::into_string(self) -> String; String::into_boxed_str(self) -> Box<str>;
Pure functions have clear inputs and outputs. There's no point in calling them if you're going to ignore the result.
f32::is_nan(self) -> bool; i32::is_negative(self) -> bool; f32::sin_cos(self) -> (f32, f32); slice::is_ascii(self) -> bool; char::is_control(self) -> bool; memchr(u8, &[u8]) -> Option<usize>; memrchr(u8, &[u8]) -> Option<usize>;
Like the pure functions, why call a getter if you don't check the result? I can't think of a good reason.
into_keys is interesting because it does have the side effect of consuming the map. One could use it to drop the map. But that's a crazy use case, no? They ought to be calling
str::len(&self) -> usize; str::is_empty(&self) -> bool; BTreeMap::keys(&self) -> Keys<K, V>; BTreeMap::into_keys(self) -> Keys<K, V>; Utf8Error::valid_up_to(&self) -> usize; Formatter::width(&self) -> Option<usize>;
It's plausible someone might write
a[i] to cause a panic if the index is out of bounds. Is that a legitimate use case or should they get a must use warning?
Index::index(&self, Idx) -> &Self::Output;
It's wasteful to allocate a new object just to throw it away.
Vec::with_capacity(usize) -> Vec<T>; Arc::downgrade(&Arc<T>) -> Weak<T>; String::from_utf16_lossy(&[u16]) -> String; Backtrace::capture() -> Backtrace; mpsc::channel() -> (Sender<T>, Receiver<T>);
These constructors are cheap. If you don't save the return values it doesn't really cost you much. It's still wasteful, but only minorly so.
Vec::new() -> Vec<T>; iter:empty() -> Empty<T>; mem::zeroed() -> T; str::from_utf_unchecked(&[u8]) -> &str; MaybeUninit::uninit() -> MaybeUninit<T>; Instant::now() -> Instant;