Free Handbook · Every example compiled & verified

Closures & Iterators

Closures and how they capture variables, Fn vs FnMut vs FnOnce, move closures, and lazy iterator chains with map, filter, fold, zip and collect.

0 / 140 lessons🔥 0 day streak
ShareXLinkedIn

Module 09 · what you'll be able to do

  • Write closures and predict whether each one borrows, mutably borrows or moves what it captures
  • Choose the right bound (Fn, FnMut or FnOnce) when a function accepts a closure
  • Use move closures and explain when they are required
  • Build iterator chains with map, filter, enumerate, zip, chain, take, skip and fold, and collect the result
  • Implement the Iterator trait for your own type and get every adapter for free
01

Closures: functions that remember their surroundings

A closure is an anonymous function you can store in a variable or pass to another function. It is written with pipes around the parameters: |x| x + 1. Unlike a normal fn, a closure can use variables from the scope where it was created. That is called capturing.

rustmain.rs
fn main() {
    let add_one = |x: i32| x + 1;          // types written out
    let square = |x| x * x;                // types inferred from first use
    let tax_rate = 0.2;
    let with_tax = |price: f64| price * (1.0 + tax_rate); // captures tax_rate

    println!("{}", add_one(4));
    println!("{}", square(7));
    println!("{:.2}", with_tax(50.0));

    let greet = |name: &str| {
        let msg = format!("Hi, {}", name);
        msg.len()
    };
    println!("{}", greet("Ferris"));
}
Outputcompiled & run with real Rust
5
49
60.00
10

A closure with a block body works like a function body: the last expression is the return value.

Your turn

Change tax_rate to 0.1 and predict the new output before running it.

Closure types are inferred once
The compiler infers a closure's parameter types from how it is first used, then locks them. Calling square(7) and later square(2.5) is a type error: the closure already takes i32. If you need it to work on several types, write a generic fn instead.
02

Capture modes and Fn, FnMut, FnOnce

A closure captures each variable in the least demanding way its body allows: by shared reference if it only reads, by mutable reference if it changes the variable, by value (move) if it consumes it. That choice decides which of three traits the closure implements:

Every Fn is also FnMut, and every FnMut is also FnOnce. A function asking for FnOnce accepts all three.
TraitBody doesCan be calledTypical use
FnOnly reads captured variablesMany times, even at the same timeiter().map(...), callbacks
FnMutChanges a captured variableMany times, one at a timeCounters, accumulators, sort_by_key
FnOnceMoves a captured value outExactly oncethread::spawn, Option::unwrap_or_else
rustmain.rs
fn apply<F: Fn(i32) -> i32>(f: F, v: i32) -> i32 {
    f(v)
}

fn repeat<F: FnMut()>(mut f: F, times: u32) {
    for _ in 0..times {
        f();
    }
}

fn consume<F: FnOnce() -> String>(f: F) -> String {
    f()
}

fn main() {
    let offset = 10;
    println!("{}", apply(|x| x + offset, 5));      // Fn: reads offset

    let mut count = 0;
    repeat(|| count += 1, 3);                       // FnMut: changes count
    println!("count = {}", count);

    let name = String::from("report.csv");
    let taken = consume(move || name);              // FnOnce: gives name away
    println!("{}", taken);
}
Outputcompiled & run with real Rust
15
count = 3
report.csv
Your turn

Try passing the move || name closure to repeat. The compiler refuses, because a closure that gives away name cannot be called more than once.

Error you will hit

E0596: calling an FnMut closure through a non-mut binding

rust
fn main() {
    let mut count = 0;
    let inc = || count += 1;
    inc();
    inc();
    println!("{}", count);
}
error[E0596]: cannot borrow `inc` as mutable, as it is not declared as mutable
 --> main.rs:3:9
  |
3 |     let inc = || count += 1;
  |         ^^^      ----- calling `inc` requires mutable binding due to mutable borrow of `count`
  |         |
  |         not mutable
4 |     inc();
  |     --- cannot borrow as mutable
5 |     inc();
  |     --- cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
3 |     let mut inc = || count += 1;
  |         +++
Why the compiler said that

The closure holds a mutable borrow of count inside itself. Calling it changes that hidden state, so the closure variable itself has to be mut, exactly like a struct whose method takes &mut self.

The fix

Declare the closure binding with let mut.

rust
fn main() {
    let mut count = 0;
    let mut inc = || count += 1;
    inc();
    inc();
    println!("{}", count);
}
Error you will hit

E0507: moving out of a captured variable in an Fn closure

rust
fn call_twice<F: Fn()>(f: F) {
    f();
    f();
}

fn main() {
    let name = String::from("Ada");
    call_twice(move || {
        let owned = name;
        println!("{}", owned);
    });
}
error[E0507]: cannot move out of `name`, a captured variable in an `Fn` closure
 --> main.rs:9:21
  |
7 |     let name = String::from("Ada");
  |         ---- captured outer variable
8 |     call_twice(move || {
  |                ------- captured by this `Fn` closure
9 |         let owned = name;
  |                     ^^^^ move occurs because `name` has type `String`, which does not implement the `Copy` trait
  |
help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but `FnOnce` closures may consume them only once
 --> main.rs:1:18
  |
1 | fn call_twice<F: Fn()>(f: F) {
  |                  ^^^^
Why the compiler said that

call_twice promises to call the closure twice, but the body moves name out, which can only happen once. After the first call there would be nothing left to move.

The fix

Borrow instead of moving inside the body (let owned = &name;), or clone. If the function really only calls it once, relax its bound to FnOnce.

rust
fn call_twice<F: Fn()>(f: F) {
    f();
    f();
}

fn main() {
    let name = String::from("Ada");
    call_twice(move || {
        let owned = &name;
        println!("{}", owned);
    });
}
03

move closures and returning closures

Putting move before the pipes forces the closure to take ownership of everything it captures, even if the body only reads it. You need this whenever the closure may outlive the current scope: returning a closure from a function, storing it in a struct, or handing it to another thread (Module 10). For Copy types like i32, "move" is just a copy.

rustmain.rs
fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
    move |x| x * factor   // factor would die at the end of this fn without move
}

fn make_counter() -> impl FnMut() -> u32 {
    let mut n = 0;
    move || {
        n += 1;
        n
    }
}

fn main() {
    let double = make_multiplier(2);
    let triple = make_multiplier(3);
    println!("{} {}", double(5), triple(5));

    let mut next_id = make_counter();
    println!("{} {} {}", next_id(), next_id(), next_id());
}
Outputcompiled & run with real Rust
10 15
1 2 3

impl Fn(i32) -> i32 means "some closure type with this call shape". Each closure has its own unnamed type, so you cannot write it out by name.

Your turn

Make a second counter with make_counter() and show that it starts at 1 again: each closure owns its own n.

Error you will hit

E0382: using a value after moving it into a closure

rust
fn main() {
    let data = vec![1, 2, 3];
    let print = move || println!("{:?}", data);
    print();
    println!("{}", data.len());
}
error[E0382]: borrow of moved value: `data`
 --> main.rs:5:20
  |
2 |     let data = vec![1, 2, 3];
  |         ---- move occurs because `data` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |     let print = move || println!("{:?}", data);
  |                 -------                  ---- variable moved due to use in closure
  |                 |
  |                 value moved into closure here
4 |     print();
5 |     println!("{}", data.len());
  |                    ^^^^ value borrowed here after move
  |
help: consider cloning the value before moving it into the closure
  |
3 ~     let value = data.clone();
4 ~     let print = move || println!("{:?}", value);
  |
Why the compiler said that

move transferred ownership of data into the closure. The original variable is now empty, the same as after let other = data;.

The fix

Drop move if the closure does not need to outlive the scope, or clone before moving as the help line suggests.

rust
fn main() {
    let data = vec![1, 2, 3];
    let print = || println!("{:?}", data);
    print();
    println!("{}", data.len());
}
04

The Iterator trait and laziness

An iterator is anything that implements the Iterator trait, whose one required method is fn next(&mut self) -> Option<Self::Item>. It returns Some(item) until it runs out, then None. A for loop is just a loop that calls next for you.

CallYieldsCollection afterwards
v.iter()&TStill usable
v.iter_mut()&mut TStill usable, possibly changed
v.into_iter() or for x in vT (owned)Moved away, gone
rustmain.rs
fn main() {
    let v = vec![10, 20, 30];
    let mut it = v.iter();
    println!("{:?}", it.next());
    println!("{:?}", it.next());
    println!("{:?}", it.next());
    println!("{:?}", it.next());

    // Adapters are lazy: this closure has not run yet.
    let noisy = v.iter().map(|x| {
        println!("mapping {}", x);
        x * 2
    });
    println!("nothing mapped yet");
    let doubled: Vec<i32> = noisy.collect(); // now it runs
    println!("{:?}", doubled);
}
Outputcompiled & run with real Rust
Some(10)
Some(20)
Some(30)
None
nothing mapped yet
mapping 10
mapping 20
mapping 30
[20, 40, 60]

Adapters like map only describe work. A consumer such as collect, sum, count or a for loop is what pulls items through.

VisualizeHow a lazy chain pulls one item at a timeStep 1 / 5
fn main() {
let v = vec![1, 2, 3, 4];
let total: i32 = v.iter().filter(|x| **x % 2 == 0).map(|x| x * 10).sum();
println!("{}", total);
}
Line 2

A vector of four numbers.

Variables now
v[1, 2, 3, 4]
All 5 steps as a table
StepLineWhat happenedVariables now
12A vector of four numbers.v = [1, 2, 3, 4]
23sum asks map for an item, map asks filter, filter asks iter. iter gives &1; it is odd, so filter asks again and gets &2.total = 0
33&2 passes the filter, map turns it into 20, sum adds it. No intermediate vector is ever built.total = 20
43&3 is filtered out, &4 passes and becomes 40. Then iter returns None and the whole chain stops.total = 60
54Print the result.total = 60
An unused adapter does nothing
Writing v.iter().map(|x| println!("{}", x)); on its own line prints nothing, and rustc warns unused `Map` that must be used. Use a for loop (or .for_each(...)) when you want side effects.
05

Adapters: map, filter, enumerate, zip, chain, take, skip

Adapters take an iterator and return a new iterator. Chaining them reads top to bottom like a data pipeline, and the compiler turns the chain into a loop as fast as one you would write by hand.

rustmain.rs
fn main() {
    let scores = vec![72, 95, 58, 88, 64, 99];

    let passed: Vec<i32> = scores.iter().copied().filter(|s| *s >= 65).collect();
    println!("passed: {:?}", passed);

    for (i, s) in scores.iter().enumerate().skip(1).take(2) {
        println!("#{} = {}", i, s);
    }

    let names = ["ana", "ben", "cy"];
    let ages = [31, 25, 40];
    let pairs: Vec<String> = names
        .iter()
        .zip(ages.iter())
        .map(|(n, a)| format!("{}:{}", n, a))
        .collect();
    println!("{}", pairs.join(", "));

    let all: Vec<i32> = (1..=3).chain(7..=8).rev().collect();
    println!("{:?}", all);
}
Outputcompiled & run with real Rust
passed: [72, 95, 88, 99]
#1 = 95
#2 = 58
ana:31, ben:25, cy:40
[8, 7, 3, 2, 1]

copied() turns &i32 items into i32, so the rest of the chain works on plain numbers. zip stops at the shorter side.

Your turn

Use .position(|s| *s < 60) on scores.iter() to print the index of the first failing score.

AdapterDoesExample
map(f)Transform each item.map(|x| x * 2)
filter(p)Keep items where the predicate is true.filter(|x| **x > 0)
filter_map(f)Map to Option, keep the Somes.filter_map(|s| s.parse().ok())
enumerate()Pair each item with its index(0, a), (1, b)
zip(other)Walk two iterators in step(a1, b1), (a2, b2)
chain(other)One iterator after another(1..3).chain(7..9)
take(n) / skip(n)First n / all but first n.skip(1).take(2)
rev()Walk backwards (double-ended iterators)(1..4).rev()
flat_map(f)Map to an iterator, then flatten.flat_map(|s| s.chars())
06

Consumers: collect, sum, fold and friends

Consumers run the chain and produce a final value. collect is the most flexible: it can build a Vec, a String, a HashMap, a BTreeMap, even a Result<Vec<T>, E> that stops at the first error. Because it can build so many things, you have to say which one, with a type annotation or the turbofish collect::<Vec<_>>().

rustmain.rs
use std::collections::BTreeMap;

fn main() {
    let words = vec!["apple", "bob", "avocado", "cat", "banana"];

    let total_len: usize = words.iter().map(|w| w.len()).sum();
    let longest = words.iter().max_by_key(|w| w.len()).unwrap();
    let has_cat = words.iter().any(|w| *w == "cat");
    println!("{} {} {}", total_len, longest, has_cat);

    // fold: start with an accumulator, combine each item into it
    let csv = words.iter().fold(String::new(), |mut acc, w| {
        if !acc.is_empty() { acc.push(','); }
        acc.push_str(w);
        acc
    });
    println!("{}", csv);

    // group by first letter; BTreeMap keeps keys sorted
    let mut by_letter: BTreeMap<char, Vec<&str>> = BTreeMap::new();
    for w in &words {
        by_letter.entry(w.chars().next().unwrap()).or_default().push(*w);
    }
    println!("{:?}", by_letter);

    let parsed: Result<Vec<i32>, _> = ["1", "2", "x"].iter().map(|s| s.parse::<i32>()).collect();
    println!("{}", parsed.is_err());
}
Outputcompiled & run with real Rust
24 avocado true
apple,bob,avocado,cat,banana
{'a': ["apple", "avocado"], 'b': ["bob", "banana"], 'c': ["cat"]}
true

Collecting into Result turns "a list of results" into "a result of a list": one bad item makes the whole thing an Err.

Your turn

Rewrite total_len with fold(0, |acc, w| acc + w.len()) and check that it prints the same number.

Error you will hit

E0283: collect does not know what to build

rust
fn main() {
    let nums = vec![1, 2, 3];
    let doubled = nums.iter().map(|n| n * 2).collect();
    println!("{:?}", doubled);
}
error[E0283]: type annotations needed
    --> main.rs:3:9
     |
   3 |     let doubled = nums.iter().map(|n| n * 2).collect();
     |         ^^^^^^^                              ------- type must be known at this point
     |
     = note: the type must implement `FromIterator<i32>`
help: consider giving `doubled` an explicit type
     |
   3 |     let doubled: Vec<_> = nums.iter().map(|n| n * 2).collect();
     |                ++++++++
Why the compiler said that

collect is generic over its return type. Nothing in the code says whether you want a Vec, a VecDeque or a HashSet, and printing with {:?} works for all of them.

The fix

Annotate the variable (let doubled: Vec<_>) or use the turbofish (.collect::<Vec<_>>()). The _ lets the compiler fill in the element type.

rust
fn main() {
    let nums = vec![1, 2, 3];
    let doubled: Vec<_> = nums.iter().map(|n| n * 2).collect();
    println!("{:?}", doubled);
}
07

Implementing Iterator for your own type

Implement next and set type Item, and your type gets every adapter and consumer above for free: map, filter, zip, sum, collect and dozens more are default methods on the trait.

rustmain.rs
struct Countdown {
    n: u32,
}

impl Iterator for Countdown {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        if self.n == 0 {
            None
        } else {
            self.n -= 1;
            Some(self.n + 1)
        }
    }
}

struct Fib {
    a: u64,
    b: u64,
}

impl Iterator for Fib {
    type Item = u64;
    fn next(&mut self) -> Option<u64> {
        let out = self.a;
        self.a = self.b;
        self.b += out;
        Some(out) // never ends: callers must use take()
    }
}

fn main() {
    for x in (Countdown { n: 3 }) {
        print!("{} ", x);
    }
    println!("liftoff");

    let evens: Vec<u64> = Fib { a: 0, b: 1 }.filter(|x| x % 2 == 0).take(5).collect();
    println!("{:?}", evens);
    let sum: u32 = Countdown { n: 10 }.sum();
    println!("{}", sum);
}
Outputcompiled & run with real Rust
3 2 1 liftoff
[0, 2, 8, 34, 144]
55

Fib is infinite, which is fine because iterators are lazy: take(5) stops asking after five matches.

Your turn

Write struct Squares { i: u32 } whose iterator yields 1, 4, 9, 16... and print the first four with take(4).

Quick check

What does this print? let v = vec![1, 2, 3]; let it = v.iter().map(|x| { println!("{}", x); x }); println!("done");

Closure
An anonymous function, written |args| body, that can capture variables from the scope where it is created.
Capture
How a closure gets access to an outer variable: by shared reference, by mutable reference, or by moving it in.
Fn / FnMut / FnOnce
The three closure traits: callable many times reading, many times mutating, or once consuming.
move closure
A closure marked move that takes ownership of everything it captures, so it can outlive the current scope.
Iterator
A type implementing next(&mut self) -> Option<Item>. Yields items until it returns None.
Adapter
A lazy method that wraps an iterator in a new one: map, filter, zip, take.
Consumer
A method that drives the iterator to the end and returns a value: collect, sum, fold, count.
Mid-levelWhy does std::thread::spawn take an FnOnce closure, while Iterator::map takes FnMut?

spawn runs the closure exactly once on the new thread, so asking only for FnOnce is the least restrictive bound: any closure qualifies, including one that moves its captured data out. map calls its closure once per item, so it needs something callable repeatedly; FnMut is the loosest trait that allows that, and it lets the closure keep state (a counter, say) between calls.

What they are really testing: Understanding that the caller picks the loosest bound matching how often and how it calls the closure.

Frequently asked questions

Are Rust iterators slower than for loops?
No. Iterator chains are "zero-cost abstractions": the compiler inlines the adapters and produces the same machine code as a hand-written loop, often better because bounds checks can be removed.
What is the difference between iter() and into_iter() in Rust?
iter() borrows the collection and yields references (&T), so you can use the collection afterwards. into_iter() takes the collection by value and yields owned items (T); the collection is moved and cannot be used again.
When do I need the move keyword on a Rust closure?
When the closure may outlive the variables it captures: returning it from a function, storing it, or passing it to thread::spawn. Without move the closure borrows, and the compiler rejects the borrow because the owner could be dropped first.

Finish the Rust handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.