Nugget 18

Home

Closure Patterns: |&x| vs |x|

You see .filter(|&x| x % 2 == 0) and .map(|x| x.parse()) side by side. Why does one have an & and the other doesn't? The answer is in the layers of references.

The Puzzle

let nums = vec![1, 2, 3, 4, 5];

// Why |&x| here?
let evens = nums.iter().filter(|&x| x % 2 == 0);

let text = "hello world";
// But |x| here?
let words: Vec<&str> = text.split_whitespace().map(|x| x).collect();

Both are closures passed to iterator methods. Both get items and do something with them. But one needs an extra & and the other doesn't. Why?

The Key: How filter and map Differ

The signatures tell the story:

// filter borrows each item — closure gets &Item
fn filter<P>(self, predicate: P) -> Filter<Self, P>
where
    P: FnMut(&Self::Item) -> bool;  // <-- NOTE: &Item

// map takes ownership of each item — closure gets Item
fn map<B, F>(self, f: F) -> Map<Self, F>
where
    F: FnMut(Self::Item) -> B;  // <-- NOTE: Item, not &Item

filter only looks at each item — it doesn't consume or transform it. So it passes a reference (&Item). map transforms and returns a new value, so it passes the item by value (Item).

Tracing the Reference Layers

When you call nums.iter() on a Vec<i32>, the iterator yields &i32. Then filter wraps that in another reference:

vec![1, 2, 3]
    .iter()        // yields &i32        — Item = &i32
    .filter(|x|)   // x: &(&i32)        — &Item = &&i32
    //     ^            ^^^^
    //     |            filter adds one layer of &
    //     |
    //     Without pattern matching, you'd write:
    //     .filter(|x| **x % 2 == 0)

So the closure receives &&i32 — a reference to a reference. That's two levels of indirection. The & in |&x| is pattern matching that destructures one layer off.

Three Styles, Same Result

These all do exactly the same thing:

let nums = vec![1, 2, 3, 4, 5];

// Style 1: Pattern matching — destructure one &
let a: Vec<_> = nums.iter().filter(|&x| x % 2 == 0).collect();

// Style 2: Explicit dereference
let b: Vec<_> = nums.iter().filter(|x| **x % 2 == 0).collect();

// Style 3: Double pattern — destructure both layers
let c: Vec<_> = nums.iter().filter(|&&x| x % 2 == 0).collect();

assert_eq!(a, b);
assert_eq!(b, c);
// All three produce [2, 4]
Style Closure receives What x is
|&x| &&i32 &i32 (destructured one &)
|x| **x &&i32 &&i32 (dereference manually)
|&&x| &&i32 i32 (destructured both layers)

Why map Doesn't Have This Problem

map receives Self::Item directly — no extra reference layer. So the closure gets exactly what the iterator yields:

// split_whitespace() yields &str
// map passes &str to the closure
// So |x| gives you x: &str directly
text.split_whitespace().map(|x| x.len()).collect();

// If you called .iter() on a Vec:
// vec.iter() yields &i32
// map passes &i32 to the closure
// So |x| gives you x: &i32 directly
nums.iter().map(|x| x * 2).collect();  // x is &i32

No double-reference because map doesn't borrow — it consumes. One less & layer.

What About Other Iterators?

The double-reference only happens when filter is called on an iterator that already yields references (like .iter() on a collection). Different iterator sources give different patterns:

// .iter() on Vec — yields &T → filter receives &&T
let v = vec![1, 2, 3];
v.iter().filter(|&x| x % 2 == 0);  // x: &i32

// .into_iter() on Vec — yields T → filter receives &T
let v = vec![1, 2, 3];
v.into_iter().filter(|&x| x % 2 == 0);  // x: i32

// Range — yields T directly → filter receives &T
(1..=3).filter(|&x| x % 2 == 0);  // x: i32

// chars() — yields char → filter receives &char
"hello".chars().filter(|&c| c != 'l');  // c: char

The rule: filter always adds one layer of &. So |&x| removes that one layer, giving you the item the iterator yields.

Key Takeaways