094 — Peekable Iterator
Tutorial
The Problem
Use .peekable() to look ahead at the next iterator element without consuming it. Implement dedup — removing consecutive duplicates — as a demonstration: consume the current value, then skip ahead while the peek matches. Compare with OCaml's manual peekable wrapper built on Seq.
🎯 Learning Outcomes
Peekable<I> with .peekable()iter.peek() to inspect the next item as Option<&Item> without advancingnext() and peek() in a while let loop for look-ahead parsingpeek() — Some(&&val) for &[i32]Peekable to OCaml's manually implemented peekable wrapperCode Example
#![allow(clippy::all)]
// 094: Peekable Iterator
fn dedup(v: &[i32]) -> Vec<i32> {
let mut result = Vec::new();
let mut iter = v.iter().peekable();
while let Some(&val) = iter.next() {
result.push(val);
while iter.peek() == Some(&&val) {
iter.next();
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dedup() {
assert_eq!(dedup(&[1, 1, 2, 2, 2, 3, 3, 1]), vec![1, 2, 3, 1]);
assert_eq!(dedup(&[]), Vec::<i32>::new());
assert_eq!(dedup(&[5]), vec![5]);
}
#[test]
fn test_peekable() {
let mut iter = [1, 2, 3].iter().peekable();
assert_eq!(iter.peek(), Some(&&1));
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.peek(), Some(&&2));
}
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Built-in | Iterator::peekable() | Manual peekable struct |
| Peek type | Option<&Item> (reference) | 'a option (value) |
| Double ref | Some(&&val) for &[T] iterators | Some x directly |
| Mutable | Internally mutable in adapter | Mutable fields in record |
| Parser use | Common in tokenisers | Same manual wrapper |
| Standard | Yes | No |
Peekable iterators are the foundation of hand-written parsers and tokenisers. By looking ahead without consuming, you can make branching decisions based on context — the backbone of recursive descent parsing.
OCaml Approach
OCaml's Seq has no built-in peek. The peekable record holds peeked: 'a option and seq: 'a Seq.t ref. peek checks the peeked slot, or forces the next thunk and caches it. next drains the peeked slot or forces the seq. This is a complete, correct implementation — just more code than Rust's built-in.
Full Source
#![allow(clippy::all)]
// 094: Peekable Iterator
fn dedup(v: &[i32]) -> Vec<i32> {
let mut result = Vec::new();
let mut iter = v.iter().peekable();
while let Some(&val) = iter.next() {
result.push(val);
while iter.peek() == Some(&&val) {
iter.next();
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dedup() {
assert_eq!(dedup(&[1, 1, 2, 2, 2, 3, 3, 1]), vec![1, 2, 3, 1]);
assert_eq!(dedup(&[]), Vec::<i32>::new());
assert_eq!(dedup(&[5]), vec![5]);
}
#[test]
fn test_peekable() {
let mut iter = [1, 2, 3].iter().peekable();
assert_eq!(iter.peek(), Some(&&1));
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.peek(), Some(&&2));
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dedup() {
assert_eq!(dedup(&[1, 1, 2, 2, 2, 3, 3, 1]), vec![1, 2, 3, 1]);
assert_eq!(dedup(&[]), Vec::<i32>::new());
assert_eq!(dedup(&[5]), vec![5]);
}
#[test]
fn test_peekable() {
let mut iter = [1, 2, 3].iter().peekable();
assert_eq!(iter.peek(), Some(&&1));
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.peek(), Some(&&2));
}
}
Deep Comparison
Core Insight
Peekable lets you inspect the next element without consuming it — essential for parsers and tokenizers
OCaml Approach
Rust Approach
Comparison Table
| Feature | OCaml | Rust |
|---|---|---|
| See | example.ml | example.rs |
Exercises
Token::Number(u64) and other characters into Token::Other(char) using a peekable iterator.group_by_peekable<T: PartialEq>(v: &[T]) -> Vec<Vec<T>> that groups consecutive equal elements using peekable.parse_csv_field(iter: &mut Peekable<impl Iterator<Item=char>>) -> String that reads until a comma or end of input.peek to implement a take_while_peek that advances as long as the peeked element satisfies a predicate, stopping before consuming the first non-matching element.peek_nth function to the peekable wrapper that allows looking n elements ahead by caching a VecDeque-style buffer.