Skip to content

Iterators: laziness and the & in every closure

Published 17 October 2026

Adapters are lazy

let parsed = ["x", "42", "7"].iter().filter_map(|s| s.parse::<i32>().ok());
println!("{:?}", parsed);
// FilterMap { iter: Iter(["x", "42", "7"]) }

filter_map, map, filter and friends return an iterator adapter (FilterMap<slice::Iter<&str>, {closure}>), not a collection. Nothing runs until something consumes it. FilterMap’s Debug prints only the wrapped iterator, which is why the original strings show up.

Consume it with collect (or sum, find, a for loop, …):

let parsed: Vec<i32> = ["x", "42", "7"]
    .iter()
    .filter_map(|s| s.parse::<i32>().ok())
    .collect();
println!("{:?}", parsed); // [42, 7]

To fail on any bad input instead of silently dropping it, collect into a Result:

let all: Result<Vec<i32>, _> = ["x", "42", "7"].iter().map(|s| s.parse::<i32>()).collect();
assert!(all.is_err()); // because of "x"

.iter() yields references

On [1, 2, 3, 4], .iter() yields &i32. So why does this compile?

let result: Vec<_> = [1, 2, 3, 4].iter().map(|n| n * 2).collect(); // Vec<i32>

n is &i32, and &i32 * i32 is defined. The standard library implements every arithmetic operator for references to primitives:

impl Mul<i32>  for i32   // i32  * i32
impl Mul<i32>  for &i32  // &i32 * i32   <- this case
impl Mul<&i32> for i32   // i32  * &i32
impl Mul<&i32> for &i32  // &i32 * &i32
// all four: Output = i32

This is not auto-deref (operators never auto-deref). It’s explicit impls, and only primitives get them. Your own Copy type needs *m * 2.

Comparison operators don’t have these impls, so the reference bites there:

[1, 2, 3].iter().filter(|n| n > 1);    // ❌ n: &&i32, can't compare with an integer
[1, 2, 3].iter().filter(|n| **n > 1);  // ✅
[1, 2, 3].iter().filter(|&&n| n > 1);  // ✅ destructure in the pattern
[1, 2, 3].iter().map(|n| n.pow(2));    // ✅ method calls do auto-deref

To skip the reference entirely: .into_iter() on an array (edition 2021+) or .iter().copied(), and items are plain i32.

filter and find add another &

Their predicates take &Self::Item, because they need to keep the item to yield or return it.

fn find<P>(&mut self, predicate: P) -> Option<Self::Item>
where
    P: FnMut(&Self::Item) -> bool,

So count the layers:

let result = [1, 2, 3, 4].iter().map(|n| n * 2).find(|n| *n > 5);
println!("{:?}", result); // Some(6)
Step Type
.iter() yields &i32
.map(\|n\| n * 2) yields i32
find’s predicate gets &i32
find returns Option<i32>

*n is right; **n fails with error[E0614]: type `i32` cannot be dereferenced. The map removed a layer. Without it you do get &&i32:

[1, 2, 3, 4].iter().find(|n| **n > 2) // Some(&3)

Equivalent predicates for the map case:

.find(|&n| n > 5)   // n: i32
.find(|n| *n > 5)
.find(|n| n > &5)   // &i32 vs &i32
// .find(|n| n > 5) // ❌ no PartialOrd<i32> for &i32

Drill: count the reference levels

Keep this rule in mind:

  • filter gets &Item, because it only checks whether to keep the item. It has to hand the item back afterwards, so it can only lend it to the predicate.
  • .iter() makes Item = &i32, so filter gets &&i32.

So the type in a filter closure is always one more & than the item type. Work out the item type first, then add one:

Chain Item filter gets A predicate that works
[1, 2, 3].iter().filter(...) &i32 &&i32 \|n\| **n > 1
[1, 2, 3].into_iter().filter(...) i32 &i32 \|n\| *n > 1
[1, 2, 3].iter().copied().filter(...) i32 &i32 \|n\| *n > 1
[1, 2, 3].iter().map(\|n\| n * 2).filter(...) i32 &i32 \|n\| *n > 2
["a", "bb"].iter().filter(...) &&str &&&str \|s\| s.len() > 1

The last row looks scary, but method calls auto-deref through any number of &, so s.len() just works. Only operators make you count.

filter doesn’t change the item type, so whatever comes after it gets the item back with the extra & gone:

let big: Vec<i32> = [1, 2, 3]
    .iter()
    .filter(|n| **n > 1) // n: &&i32
    .map(|n| n * 10)     // n: &i32 again
    .collect();          // [20, 30]

When you’d rather not count, put the &s in the pattern instead: |&&n| n > 1 on .iter(), |&n| n > 1 after .copied() or .into_iter(). Each & in the pattern peels off one layer.

map, find_map and friends

map transforms every item. find_map transforms each item into an Option and stops at the first Some. It’s filter_map(...).next(), but it says “first successful result” directly.

Method Closure gets Closure returns Result
map Item U lazy iterator of U
filter_map Item Option<U> lazy iterator of every Some
find_map Item Option<U> Option<U>: the first Some, then stops
flat_map Item an iterator lazy iterator, flattened
find &Item bool Option<Item>: the first match
position Item bool Option<usize>: index of the first match
any Item bool bool

Note the & column: find (like filter) gets &Item because it keeps the item to return it. position and any only return an index or a bool, so they take the item itself. On .iter(), that’s **n in find but *n in position.

And the three ways in: iter() yields &T, iter_mut() yields &mut T, into_iter() yields T.

Quiz

let a: Vec<_> = ["x", "42", "7"].iter().filter_map(|s| s.parse::<i32>().ok()).collect();
// [42, 7]: skips "x", keeps every success

let b: Option<i32> = ["x", "42", "7"].iter().find_map(|s| s.parse::<i32>().ok());
// Some(42): stops at the first success; None if nothing parses

let c = [1, 2, 3, 4].iter().find(|n| **n > 2);
// Some(&3): find returns the item, and .iter() items are references

let d = [1, 2, 3, 4].iter().position(|n| *n > 2);
// Some(2): Option<usize>, the zero-based index of 3, not the value

let e: Vec<_> = [1, 2, 3, 4].iter().flat_map(|n| 0..*n).collect();
// [0, 0, 1, 0, 1, 2, 0, 1, 2, 3]: 0..1, 0..2, 0..3, 0..4 joined

The trap: c and d both print Some(...), but one is a reference to a value and the other is an index.

Asking the compiler for a type

Assign to the wrong type and read the error (or hover in rust-analyzer):

let x: () = [1, 2, 3, 4].iter().map(|n| n * 2);
// expected `()`, found `Map<Iter<'_, {integer}>, {closure}>`

map returns a lazy Map whose Item = i32; collect() turns it into Vec<i32>.

See also &[&str] vs [&str] for .iter() on a slice of &str.