Error-Handling

69. Iterator::try_fold — Fold That Knows When to Stop

Need to accumulate values from an iterator but bail on the first error? try_fold gives you the power of fold with the early-exit behavior of ?.

The problem

You’re parsing a list of strings into numbers and summing them. With fold, you have no clean way to short-circuit on a parse failure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
    let inputs = vec!["10", "20", "oops", "40"];

    // This doesn't compile — fold expects the same type every iteration
    // and there's no way to bail early
    let mut total = 0i64;
    let mut error = None;
    for s in &inputs {
        match s.parse::<i64>() {
            Ok(n) => total += n,
            Err(e) => {
                error = Some(e);
                break;
            }
        }
    }

    assert!(error.is_some());
    assert_eq!(total, 30); // partial sum before the error
}

It works, but you’ve traded iterator chains for mutable state and a manual loop.

The clean way

try_fold takes an initial accumulator and a closure that returns Result<Acc, E> (or any type implementing Try). It stops at the first Err and returns it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn sum_parsed(inputs: &[&str]) -> Result<i64, std::num::ParseIntError> {
    inputs.iter().try_fold(0i64, |acc, s| {
        let n = s.parse::<i64>()?;
        Ok(acc + n)
    })
}

fn main() {
    // All valid — returns the sum
    assert_eq!(sum_parsed(&["10", "20", "30"]), Ok(60));

    // Stops at "oops", never touches "40"
    assert!(sum_parsed(&["10", "20", "oops", "40"]).is_err());
}

No mutable variables, no manual loop, no tracking partial state. The ? inside the closure does exactly what you’d expect.

It works with Option too

try_fold works with any Try type. With Option, it stops at the first None:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let values = vec![Some(1), Some(2), Some(3)];
    let sum = values.iter().try_fold(0, |acc, opt| {
        opt.map(|n| acc + n)
    });
    assert_eq!(sum, Some(6));

    let values = vec![Some(1), None, Some(3)];
    let sum = values.iter().try_fold(0, |acc, opt| {
        opt.map(|n| acc + n)
    });
    assert_eq!(sum, None); // stopped at None
}

Bonus: try_for_each

If you don’t need an accumulator and just want to run a fallible operation on each element, try_for_each is the shorthand:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn validate_all(inputs: &[&str]) -> Result<(), std::num::ParseIntError> {
    inputs.iter().try_for_each(|s| {
        s.parse::<i64>()?;
        Ok(())
    })
}

fn main() {
    assert!(validate_all(&["1", "2", "3"]).is_ok());
    assert!(validate_all(&["1", "nope", "3"]).is_err());
}

Both methods are lazy — they only consume as many elements as needed. When your fold can fail, reach for try_fold instead of a manual loop.

38. #[must_use] — Never Ignore What Matters

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.