#211 Jun 19, 2026

211. Iterator::inspect — Debug a Lazy Chain Without Tearing It Apart

When a map/filter chain gives the wrong answer, the reflex is to rip it into a for loop just to drop in a println!. You don’t have to — inspect lets you peek at every value as it flows past.

inspect runs a closure on each item and then hands the same item downstream, untouched. It’s a tap on the pipe: perfect for seeing what survives each stage without restructuring anything.

Say this sum is wrong and you want to know which numbers actually make it through the filter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let nums = [1, 2, 3, 4, 5, 6];

let sum: i32 = nums
    .iter()
    .filter(|&&n| n % 2 == 0)
    .inspect(|n| println!("kept: {n}"))
    .map(|&n| n * 10)
    .inspect(|n| println!("scaled: {n}"))
    .sum();

assert_eq!(sum, 120); // (2 + 4 + 6) * 10

You can drop a tap between any two adapters, as many as you like, and the result is identical to the chain without them. Because iterators are lazy, the closures fire only as items are pulled — so the prints interleave with the real work instead of dumping everything up front.

The key property is that inspect is transparent: it borrows each item (&T) and returns the iterator unchanged, so types and values flow through exactly as before:

1
2
3
4
5
6
let evens: Vec<i32> = (1..=6)
    .inspect(|n| eprintln!("saw: {n}"))
    .filter(|n| n % 2 == 0)
    .collect();

assert_eq!(evens, [2, 4, 6]);

Reach for eprintln! inside the closure so debug output goes to stderr and stays out of any data you’re printing to stdout. And when you’re done, deleting the tap is a one-line change — not an unwind of the loop you’d otherwise have written.

← Previous 210. next_power_of_two — Round Up to a Power of Two Without the Bit-Twiddling