Closures and Iterators
A closure is a function without a name that you can store in a variable or pass to another function, and that can use the variables around it. An iterator is anything that hands out values one at a time. The two go together: most iterator methods take a closure that says what to do with each value, and chaining them replaces a lot of hand-written loops.
This lesson covers closure syntax, how closures capture variables, the Fn,
FnMut and FnOnce traits, move closures and returning closures from
functions, then the iterator methods you will use every day: iter and its
siblings, adapters like map and filter, and consumers like collect and
sum.
Closure syntax
A closure lists its parameters between two pipes, followed by its body:
fn main() {
let greet = |name: &str| format!("Hello, {name}!");
let is_short = |word: &str| word.len() <= 3;
println!("{}", greet("Ferris"));
println!("{}", is_short("crab"));
}Hello, Ferris!
falseYou call a closure like a function: greet("Ferris"). The body is a single
expression here; for more than one statement use a block, exactly as in a
function body:
fn main() {
let describe = |celsius: f64| -> String {
let fahrenheit = celsius * 1.8 + 32.0;
format!("{celsius}°C is {fahrenheit}°F")
};
println!("{}", describe(20.0));
}20°C is 68°FUnlike functions, closures don't need type annotations. The compiler infers
parameter and return types from how the closure is used, so short closures
are usually written without them. A closure with no parameters has empty
pipes, ||.
The same logic written as a function and as closures, from most to least explicit:
fn add_one_fn(x: u32) -> u32 {
x + 1
}
fn main() {
let add_one_v1 = |x: u32| -> u32 { x + 1 };
let add_one_v2 = |x: u32| x + 1;
let add_one_v3 = |x| x + 1;
println!("{} {}", add_one_fn(1), add_one_v1(2));
println!("{} {}", add_one_v2(3), add_one_v3(4));
}2 3
4 5The function must spell out every type. add_one_v1 does too, add_one_v2
lets the return type be inferred, and add_one_v3 infers the parameter type
from the call as well.
Inference picks one type per closure. Once a closure has been called with a
u32, it takes u32s from then on; a closure is not generic.
Capturing the environment
What makes closures more than short functions is that they can use variables from the scope they are defined in:
fn main() {
let min_len = 5;
let long_enough = |word: &str| word.len() >= min_len;
println!("{}", long_enough("tea"));
println!("{}", long_enough("espresso"));
}false
truemin_len isn't a parameter. The closure captures it from main. A
nested fn can't do this; it only sees its own parameters and globals.
A closure captures each variable in the least demanding way its body allows:
- By shared reference when it only reads the variable, as above.
- By mutable reference when it changes the variable.
- By value when it moves the variable somewhere.
Here the closure pushes onto log, so it borrows log mutably:
fn main() {
let mut log = Vec::new();
let mut record = |event: &str| log.push(event.to_string());
record("started");
record("stopped");
println!("{log:?}");
}["started", "stopped"]Two things to notice. The closure variable itself is let mut record,
because calling it changes its captured state. And log can't be read while
record is still going to be used: the mutable borrow lasts until the
closure's last call, exactly like a &mut reference. After record("stopped")
the borrow is over and println! can read log again.
When the body gives away ownership of a captured variable, the closure captures it by value, and can then only be called once:
fn main() {
let tags = vec![String::from("rust"), String::from("closures")];
let into_title = || {
let mut words = tags;
words.sort();
words.join(" & ")
};
println!("{}", into_title());
}closures & rustlet mut words = tags; moves tags into the closure body. After the first
call there is nothing left to move, so a second call would be an error.
Fn, FnMut and FnOnce
Every closure has its own anonymous type, which you can't write down. What you can name is the traits it implements. There are three, and they follow from how the closure uses what it captured:
| Trait | The closure... | Can be called |
|---|---|---|
Fn | only reads its captures | any number |
FnMut | changes its captures | any number |
FnOnce | moves a capture out of its body | once |
They nest: every Fn closure is also FnMut, and every closure is
FnOnce, since anything that can be called many times can be called once.
The compiler picks the traits for you from the body; you don't declare them
on the closure.
Where you do write them is on functions that take closures. A parameter with
a closure type is a generic parameter with an Fn* bound, and the arguments
and return type go in the bound, like FnMut(u32) or Fn(&str) -> bool:
fn repeat<F>(times: u32, mut action: F)
where
F: FnMut(u32),
{
for round in 1..=times {
action(round);
}
}
fn main() {
let mut squares = Vec::new();
repeat(4, |n| squares.push(n * n));
println!("{squares:?}");
repeat(2, |n| println!("round {n}"));
}[1, 4, 9, 16]
round 1
round 2repeat asks for FnMut because it calls action several times and doesn't
care whether it changes anything. The first closure mutates squares, the
second only prints; both are FnMut, so both are accepted. The parameter is
mut action because calling an FnMut needs mutable access to it.
Pick the loosest bound that lets your function do its job:
FnOnceif you call it at most once. This accepts every closure.Option::unwrap_or_elsetakes one, since it calls its fallback at most once.FnMutif you call it several times, and it may keep state between calls.Fnif you call it several times and need it not to change anything, for example because you call it through a shared reference.
The impl Trait shorthand from the generics lesson works here too:
fn repeat(times: u32, mut action: impl FnMut(u32)).
Plain functions implement all three traits, so you can pass a function's name wherever a closure is expected:
fn is_vowel(c: char) -> bool {
"aeiou".contains(c)
}
fn count_matching(text: &str, test: impl Fn(char) -> bool) -> usize {
let mut count = 0;
for c in text.chars() {
if test(c) {
count += 1;
}
}
count
}
fn main() {
println!("{}", count_matching("ownership", is_vowel));
println!("{}", count_matching("ownership", |c| c == 'p' || c == 'h'));
}3
2move closures
By default a closure borrows what it can. The move keyword makes it take
ownership of every variable it captures instead, even ones it only reads.
You need this when the closure outlives the scope it was created in. The most common case is returning a closure from a function:
fn make_greeter(greeting: String) -> impl Fn(&str) -> String {
move |name| format!("{greeting}, {name}!")
}
fn main() {
let hello = make_greeter(String::from("Hello"));
let welcome = make_greeter(String::from("Welcome back"));
println!("{}", hello("Ferris"));
println!("{}", welcome("Ada"));
}Hello, Ferris!
Welcome back, Ada!greeting is a local of make_greeter and is gone when it returns. Without
move the closure would hold a reference to it; with move the closure owns
the String and carries it along. The return type is impl Fn(&str) -> String, "some closure type that can be called with a &str", since the
real type has no name.
Note that move is about how variables are captured, not about which trait
the closure gets. This closure owns greeting but only reads it, so it is
still Fn and can be called many times. The other place you will need move
is when handing a closure to a new thread, covered in the concurrency lesson.
Iterators
An iterator is a value that implements the Iterator trait from the advanced
traits lesson: a next method that returns Some(item) until the sequence
runs out, then None. A for loop is a loop that calls next for you.
Collections give you three iterators, which differ in what they yield:
| Method | Yields | The collection afterwards |
|---|---|---|
.iter() | &T | unchanged, still usable |
.iter_mut() | &mut T | changed in place |
.into_iter() | T | consumed, no longer usable |
for x in &v is v.iter(), for x in &mut v is v.iter_mut(), and
for x in v is v.into_iter().
Other types have their own iterators too: text.chars(), text.lines(),
text.split(','), map.keys(), and ranges like 1..=10 are iterators
themselves.
Iterator methods come in two kinds. Adapters take an iterator and return a new one that changes the values on the way through. Consumers pull all the values out and produce a result.
Adapters
map transforms each value, filter keeps the values for which a closure
returns true:
fn main() {
let readings = vec![18.5, 21.0, 23.5, 19.0, 25.5];
let warm: Vec<String> = readings
.iter()
.filter(|&&celsius| celsius > 20.0)
.map(|celsius| format!("{celsius}°C"))
.collect();
println!("{warm:?}");
}["21°C", "23.5°C", "25.5°C"]Read it top to bottom: take the readings, keep those above 20, format each
one, collect the results into a Vec.
Mind the references. readings.iter() yields &f64, and filter passes its
closure a reference to each item, so the closure receives &&f64. The pattern
|&&celsius| strips both references off to get a plain f64. Writing
|celsius| **celsius > 20.0 is the same thing. map receives the item
itself, a &f64, and format! prints references just like values.
Values that come out of map don't have to be the same type as the values
that went in. filter_map does both jobs at once: its closure returns an
Option, None drops the item and Some(x) keeps x. It fits parsing,
where some inputs are invalid:
fn main() {
let lines = ["width=80", "height=24", "garbage", "depth=3"];
let values: Vec<u32> = lines
.iter()
.filter_map(|line| line.split_once('='))
.filter_map(|(_, value)| value.parse().ok())
.collect();
println!("{values:?}");
}[80, 24, 3]A few more adapters you will use often:
| Adapter | Produces |
|---|---|
map(f) | f(x) for each x |
filter(p) | the items where p(&x) is true |
filter_map(f) | y for each x where f(x) is Some(y) |
enumerate() | (index, x) pairs, starting at 0 |
zip(other) | (x, y) pairs from two iterators, until either ends |
take(n) / skip(n) | the first n items / everything after them |
rev() | the items in reverse order |
chain(other) | this iterator's items, then the other's |
flat_map(f) | the items of each iterator f(x) returns, in order |
Several at once:
fn main() {
let names = ["Ada", "Grace", "Linus", "Ferris"];
let scores = [72, 95, 88, 64];
for (rank, (name, score)) in names.iter().zip(scores).enumerate().skip(1).take(2) {
println!("{}. {name}: {score}", rank + 1);
}
}2. Grace: 95
3. Linus: 88Iterators are lazy
Adapters don't do any work when you call them. map and filter only build
a new iterator that remembers the closure; nothing runs until something asks
for values with next. That something is a for loop or a consumer.
This makes chains cheap: each value goes through the whole chain before the
next one starts, and no intermediate Vec is built between the steps. It also
makes infinite iterators useful. (1..) counts up forever, and is fine as
long as something stops asking:
fn main() {
let first_multiples: Vec<u64> = (1..).map(|n| n * 7).take(5).collect();
println!("{first_multiples:?}");
}[7, 14, 21, 28, 35]Consumers
Consumers drive the iterator to the end (or until they have their answer) and return a single value:
fn main() {
let prices = [4.5, 12.0, 7.25, 30.0];
let total: f64 = prices.iter().sum();
let count = prices.iter().filter(|&&p| p > 5.0).count();
let any_free = prices.iter().any(|&p| p == 0.0);
let first_big = prices.iter().find(|&&p| p > 10.0);
let position = prices.iter().position(|&p| p == 7.25);
let cheapest = prices.iter().copied().fold(f64::MAX, f64::min);
println!("total {total}, {count} over 5, free: {any_free}");
println!("first over 10: {first_big:?}, 7.25 at {position:?}, cheapest {cheapest}");
}total 53.75, 3 over 5, free: false
first over 10: Some(12.0), 7.25 at Some(2), cheapest 4.5| Consumer | Returns |
|---|---|
collect() | a collection built from the items |
sum() / product() | the items added / multiplied together |
count() | how many items there were |
min() / max() | Option of the smallest / largest (for Ord items) |
any(p) / all(p) | whether p is true for some / every item |
find(p) | Option of the first item where p is true |
position(p) | Option of the index of that item |
fold(init, f) | init combined with every item in turn by f(acc, x) |
last() | Option of the final item |
fold is the general one: it starts from an initial value and folds each
item into it. sum is a fold with 0 and +. The example uses it for the
minimum because f64 isn't Ord (NaN is neither smaller nor larger than
anything), so min() isn't available on floats.
any, all, find and position stop as soon as they know the answer, so
they don't necessarily look at every item.
collect
collect can build many different collections, so it needs to know which
one. Say it with a type annotation on the variable or with the turbofish:
use std::collections::HashMap;
fn main() {
let word = "iterator";
let letters: Vec<char> = word.chars().collect();
let shouted = word.chars().rev().collect::<String>();
let stock: HashMap<&str, u32> = ["apples", "pears"].into_iter().zip([12, 7]).collect();
println!("{letters:?}");
println!("{shouted}");
println!("{} apples, {} pears", stock["apples"], stock["pears"]);
}['i', 't', 'e', 'r', 'a', 't', 'o', 'r']
rotareti
12 apples, 7 pearsA Vec, a String from chars or &strs, a HashMap from (key, value)
pairs, a HashSet or BTreeSet from values: any type implementing
FromIterator works. Vec<_> is enough when the item type can be inferred.
One more trick: collecting an iterator of Results into a
Result<Vec<_>, _> stops at the first Err and returns it, otherwise it
returns all the Ok values:
fn main() {
let good: Result<Vec<u8>, _> = "3,1,4".split(',').map(|s| s.parse::<u8>()).collect();
let bad: Result<Vec<u8>, _> = "3,x,4".split(',').map(|s| s.parse::<u8>()).collect();
println!("{good:?}");
println!("{bad:?}");
}Ok([3, 1, 4])
Err(ParseIntError { kind: InvalidDigit })Functions that take and return iterators
To accept any iterator, use a generic parameter bounded by Iterator (or
IntoIterator, which also accepts collections and arrays directly), with the
item type in the bound:
fn longest(words: impl IntoIterator<Item = String>) -> Option<String> {
words.into_iter().max_by_key(|word| word.len())
}
fn main() {
let from_vec = longest(vec![String::from("map"), String::from("filter")]);
let from_split = longest("zip chain enumerate".split(' ').map(String::from));
println!("{from_vec:?}");
println!("{from_split:?}");
}Some("filter")
Some("enumerate")Returning an iterator uses impl Iterator<Item = T>, as in the generics
lesson. Chains of adapters and closures have types nobody can write, so
impl Trait is the only practical way.
Iterator chains aren't slower than the equivalent loop. The compiler inlines the closures and adapters into roughly the loop you would have written by hand, often with bounds checks removed. Choose between a chain and a loop by which one reads better.
Common mistakes
Using a closure with two different types
fn main() {
let wrap = |x| Some(x);
let number = wrap(5);
let text = wrap("five");
println!("{number:?} {text:?}");
}The first call fixes x as an integer, and a closure has only one type. If
you need it for several types, write a generic function instead.
Using a captured variable in a nested function
fn main() {
let min_len = 5;
fn long_enough(word: &str) -> bool {
word.len() >= min_len
}
println!("{}", long_enough("espresso"));
}Only closures capture. Turn it into a closure, as the compiler suggests, or
pass min_len as a parameter.
Mutating from a closure where Fn is required
fn call_three_times(f: impl Fn()) {
f();
f();
f();
}
fn main() {
let mut calls = 0;
call_three_times(|| calls += 1);
println!("{calls}");
}The closure changes calls, so it is only FnMut, and the function promised
it would accept nothing weaker than Fn. If the function doesn't need Fn,
relax the bound to impl FnMut() and make the parameter mut f.
Calling a consuming closure twice
fn main() {
let tags = vec![String::from("rust"), String::from("closures")];
let into_title = || {
let mut words = tags;
words.sort();
words.join(" & ")
};
println!("{}", into_title());
println!("{}", into_title());
}The closure moves tags out when it runs, so it is only FnOnce. If it must
run more than once, don't move the capture: work on a clone
(let mut words = tags.clone();) or borrow it (tags.join(" & ")).
Returning a closure without move
fn make_greeter(greeting: String) -> impl Fn(&str) -> String {
|name| format!("{greeting}, {name}!")
}
fn main() {
let hello = make_greeter(String::from("Hello"));
println!("{}", hello("Ferris"));
}The closure only reads greeting, so it borrows it, but greeting is
dropped when the function returns. move |name| ... gives the closure its
own copy to keep.
Comparing through references in filter
fn main() {
let readings = vec![18.5, 21.0, 23.5];
let warm: Vec<&f64> = readings.iter().filter(|celsius| celsius > 20.0).collect();
println!("{warm:?}");
}iter() yields &f64 and filter hands its closure a reference to that, a
&&f64, which can't be compared to a plain f64. Destructure it in the
pattern (|&&celsius| celsius > 20.0) or dereference it
(|celsius| **celsius > 20.0).
Forgetting that adapters are lazy
fn main() {
let names = ["Ada", "Grace"];
names.iter().map(|name| println!("Hello, {name}!"));
}This compiles, runs and prints nothing: map built an iterator that nobody
asked for values. Heed the warning. For side effects like printing, a for
loop is the clearest fix (for name in names), or end the chain with
.for_each(...).
Summary
- A closure is an anonymous function,
|args| body, whose parameter and return types are usually inferred. Each closure has exactly one type. - Closures capture the variables they use: by shared reference, by mutable
reference or by value, whichever the body needs.
moveforces capture by value, needed when the closure outlives its scope. Fnclosures only read their captures,FnMutones change them,FnOnceones consume them and can be called once. Functions taking closures bound a generic on the loosest trait they can accept.iter(),iter_mut()andinto_iter()yield&T,&mut TandT.- Adapters (
map,filter,filter_map,enumerate,zip,take, ...) are lazy and build new iterators. Consumers (collect,sum,count,find,any,fold, ...) run the chain and produce a value. collectneeds to be told what to build, and can collectResults into aResultof a collection.
Practise what you read
Challenges that use the ideas from this lesson.