Rust Peekable Iterators
A peekable iterator can look at its next item without consuming it. peek() shows the next item and leaves it in place.
Peek without eating
peek shows the next item. next still hands over that same item:
let mut it = vec![10, 20, 30].into_iter().peekable();
println!("peek = {:?}", it.peek());
println!("next = {:?}", it.next());
println!("peek = {:?}", it.peek());Let the next item decide
A marker word "//" means the word after it is a label. Peeking is not even needed to consume the marker — the point is the loop reads pairs with confidence:
let words = ["//", "a", "b", "//", "c"];
let mut it = words.iter().peekable();
let mut labels = vec![];
while let Some(w) = it.next() {
if *w == "//" {
labels.push(format!("note: {}", it.next().unwrap()));
}
}
println!("{labels:?}");Take only if it fits
next_if checks the waiting item first. It takes the item when the test passes, and leaves it in place when it fails:
let mut it = vec![1, 2, 3].into_iter().peekable();
let first = it.next_if(|n| *n < 2);
let second = it.next_if(|n| *n < 2);
println!("{first:?} {second:?} next = {:?}", it.next());Example
fn count_groups(line: &str) -> usize {
let mut chars = line.chars().peekable();
let mut groups = 0;
while let Some(c) = chars.next() {
if c.is_alphabetic() {
groups += 1;
while chars.peek().is_some_and(|n| n.is_alphabetic()) {
chars.next();
}
}
}
groups
}
fn main() {
for line in ["one two three", "hello, world!", "42", ""] {
println!("{line:?} -> {}", count_groups(line));
}
}Key points
- Use peekable() to wrap any iterator so it gains peek()
- peek() returns Option<&Item>.
- You get Some of the next item without consuming it.
- At the end you get None.
- Call peek ten times and the item is still there
- The iterator variable must be mut: peek may need to pull one item and hold it ready
- Use peek_mut() when you need to change the waiting item before it is consumed.
- It hands back a &mut to it
- Use next_if(test) to take the next item only when it passes the test.
- Use next_if_eq(&value) to take it only when it equals a value
In this example, count_groups counts runs of letters in a line. The outer loop takes one character at a time. When a letter starts a group, the count rises by 1. Then the inner loop uses peek. It keeps eating characters only while the next one is also a letter. peek answers "is the word finished?" without eating the space or comma that ends it. So "one two three" holds 3 groups, "hello, world!" holds 2, and lines with no letters hold 0. is_some_and is a small Option helper: it runs the test only when the peeked value is Some.
Note
peek over .iter() gives a double reference, Option<&&T> — one & from peek and one from the borrow iterator.
A pattern like |&&x| untangles it, the same trick find used in Consuming Iterators.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What does peek() return once the iterator is finished?