Rust’s #[must_use] attribute turns silent bugs into compile-time warnings — making sure important return values never get accidentally ignored.
The Problem: Silently Ignoring Results
Here’s a classic bug that can haunt any codebase:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| fn remove_expired_tokens(tokens: &mut Vec<String>) -> usize {
let before = tokens.len();
tokens.retain(|t| !t.starts_with("exp_"));
before - tokens.len()
}
fn main() {
let mut tokens = vec![
"exp_abc".to_string(),
"valid_xyz".to_string(),
"exp_def".to_string(),
];
// Bug: we call the function but ignore the count!
remove_expired_tokens(&mut tokens);
// No warning, no error — the return value just vanishes
}
|
The function works fine, but the caller threw away useful information without even a whisper from the compiler.
The Fix: #[must_use]
Add #[must_use] to the function and the compiler has your back:
1
2
3
4
5
6
| #[must_use = "returns the number of removed tokens"]
fn remove_expired_tokens(tokens: &mut Vec<String>) -> usize {
let before = tokens.len();
tokens.retain(|t| !t.starts_with("exp_"));
before - tokens.len()
}
|
Now if someone calls remove_expired_tokens(&mut tokens); without using the result, the compiler emits:
1
2
3
4
| warning: unused return value of `remove_expired_tokens` that must be used
--> src/main.rs:14:5
|
= note: returns the number of removed tokens
|
Works on Types Too
#[must_use] isn’t just for functions — it shines on types:
1
2
3
4
5
| #[must_use = "this Result may contain an error that should be handled"]
enum DatabaseResult<T> {
Ok(T),
Err(String),
}
|
This is exactly why calling .map() on an iterator without collecting produces a warning — Map is marked #[must_use] in std.
Already in the Standard Library
Rust’s standard library uses #[must_use] extensively. Result, Option, MutexGuard, and many iterator adapters are all marked with it. That’s why you get a warning for:
1
| vec![1, 2, 3].iter().map(|x| x * 2); // warning: unused `Map`
|
The iterator does nothing until consumed — and #[must_use] makes sure you don’t forget.
Quick Rules
Use #[must_use] when:
- A function returns a
Result or error indicator — callers should handle failures - A function is pure (no side effects) — ignoring the return means the call was pointless
- A type is lazy (like iterators) — it does nothing until consumed
- The return value carries critical information the caller likely needs
The custom message string is optional but highly recommended — it tells the developer why they shouldn’t ignore the value.