fuse() and inspect()
The .fuse() and .inspect() adapters wrap an iterator without changing its yielded items: .fuse() guarantees that an iterator permanently yields None after its first None, and .inspect() runs a side-effecting closure on a shared reference to each item as it flows through a pipeline.
Ensuring Terminal Exhaustion with .fuse()
The Iterator trait contract does not strictly forbid a custom iterator from returning Some(x) again if .next() is called after an earlier call returned None. Wrapping an iterator in .fuse() returns a std::iter::Fuse<Self> (which implements the FusedIterator marker trait) that latches permanently on the first None and never polls the underlying iterator again:
struct FlakyAlternator {
step: u8,
}
impl Iterator for FlakyAlternator {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
self.step += 1;
// Returns None on even steps, but resumes returning Some on odd steps!
if self.step % 2 == 1 {
Some(self.step)
} else {
None
}
}
}
fn main() {
let mut raw = FlakyAlternator { step: 0 };
println!("Raw: {:?}, {:?}, {:?}", raw.next(), raw.next(), raw.next());
let mut fused = FlakyAlternator { step: 0 }.fuse();
println!(
"Fused: {:?}, {:?}, {:?}",
fused.next(),
fused.next(),
fused.next()
);
}
Raw: Some(1), None, Some(3)
Fused: Some(1), None, None
Observing Pipeline Stages with .inspect()
The .inspect(f) adapter takes a closure FnMut(&Self::Item) and calls it with a shared reference &item before passing item unchanged to the next adapter. This makes .inspect() ideal for logging, debugging, or counting intermediate stages inside a multi-step iterator chain:
fn main() {
let sum: i32 = [1, 2, 3, 4]
.into_iter()
.inspect(|x| println!("before filter: {x}"))
.filter(|x| x % 2 == 0)
.inspect(|x| println!(" after filter: {x}"))
.map(|x| x * 10)
.sum();
println!("Total: {sum}");
}
before filter: 1
before filter: 2
after filter: 2
before filter: 3
before filter: 4
after filter: 4
Total: 60