Peekable

52. Peekable::next_if_map — Peek, Match, and Transform in One Step

Tired of peeking at the next element, checking if it matches, and then consuming and transforming it? next_if_map collapses that entire dance into a single call.

The problem

When writing parsers or processing token streams, you often need to conditionally consume the next element and extract something from it. With next_if and peek, you end up doing the work twice — once to check, once to transform:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let mut iter = vec![1_i64, 2, -3, 4].into_iter().peekable();

// Clunky: peek, check, consume, transform — separately
let val = if iter.peek().is_some_and(|n| *n > 0) {
    iter.next().map(|n| n * 10)
} else {
    None
};

assert_eq!(val, Some(10));

It works, but you’re expressing the same logic in two places and the code doesn’t clearly convey its intent.

Enter next_if_map

Stabilized in Rust 1.94, Peekable::next_if_map takes a closure that returns Result<R, I::Item>. Return Ok(transformed) to consume the element, or Err(original) to put it back:

1
2
3
4
5
6
7
8
9
let mut iter = vec![1_i64, 2, -3, 4].into_iter().peekable();

// Clean: one closure handles the check AND the transformation
let val = iter.next_if_map(|n| {
    if n > 0 { Ok(n * 10) } else { Err(n) }
});

assert_eq!(val, Some(10));
assert_eq!(iter.next(), Some(2)); // iterator continues normally

The element is only consumed when you return Ok. If you return Err, the original value goes back and the iterator is unchanged.

Parsing example: extract leading digits

This is where next_if_map really shines — pulling typed tokens out of a character stream:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let mut chars = "42abc".chars().peekable();

let mut number = 0u32;
while let Some(digit) = chars.next_if_map(|c| {
    c.to_digit(10).ok_or(c)
}) {
    number = number * 10 + digit;
}

assert_eq!(number, 42);
assert_eq!(chars.next(), Some('a')); // non-digit stays unconsumed

Each character is inspected once: digits are consumed and converted, and the first non-digit stops the loop without being eaten.

Key details

  • Atomic peek + consume + transform: no redundant checks, no repeated logic
  • Non-destructive on rejection: returning Err(item) puts the element back
  • Also available: next_if_map_mut takes FnOnce(&mut I::Item) -> Option<R> for when you don’t need ownership
  • Stable since Rust 1.94

Next time you’re writing a peek-then-consume pattern, reach for next_if_map — your parser will thank you.