Closures and iterators
Traits and lifetimes are in place, so a closure can say how it captures and how long that capture lasts. Learn the closure first. Iterator adapters are the second section, because map and filter take closures. The third section is the adapters you reach for after map and filter feel ordinary.
Closures
A closure is an anonymous function that can use variables from the scope around it. let add_x = |n| n + x captures x. The forms are || expression, |a, b| expression, and |a: i32| -> i32 { a + 1 }.
| Trait | The closure | How often |
|---|---|---|
| Fn | Only reads the capture | Many times, including through a shared reference |
| FnMut | Assigns through a captured mut binding | Many times, but not through a shared reference |
| FnOnce | Moves a capture out | Once |
A closure that only looks at x implements Fn, and therefore also FnMut and FnOnce. You can call it many times.
Returning a closure is impl Fn(i32) -> i32, the opaque type from traits. Box<dyn Fn()> is the erased form when the concrete closure cannot be named. You already used closures without noticing: unwrap_or_else, and HashMap’s or_insert_with. Iterator adapters are the place you will write them on purpose.
fn apply_twice(f: impl Fn(i32) -> i32, n: i32) -> i32 {
f(f(n))
}
fn mul(k: i32) -> impl Fn(i32) -> i32 {
move |n| n * k
}
fn main() {
let add_two = |n| n + 2;
println!("{}", apply_twice(add_two, 3));
let mut acc = 0;
let mut add_to_acc = |n| {
acc += n;
acc
};
println!("{}", add_to_acc(4));
let owned = String::from("hi");
let take = move || owned.len();
println!("{}", take());
println!("{}", mul(3)(4));
}Iterators
An iterator produces the next item, or ends. Adapters such as filter and map are lazy: they build a pipeline and do no work until a consumer asks. Consumers include sum, collect, for, find, and fold.
iter borrows. iter_mut borrows mutably. into_iter consumes, which you already chose on a Vec. Nothing is filtered until something pulls.
fn main() {
let nums = [1, 2, 3, 4, 5];
let sum: i32 = nums.iter().filter(|n| *n % 2 == 0).map(|n| *n * 10).sum();
let labels: Vec<String> = nums.iter().map(|n| format!("n={n}")).collect();
let found = nums.iter().find(|n| **n == 4).copied();
println!("{sum} {} {:?}", labels.len(), found);
}More adapters
| Adapter | What it does |
|---|---|
| windows(n), chunks(n) | Look at adjacent groups of a slice |
| enumerate | Pairs an index with the item |
| zip | Pairs two iterators and stops at the shorter one |
| flat_map | Turns each item into an iterator and flattens one level |
| partition | Splits into two collections with a predicate |
| next | The method a custom iterator implements |
| from_fn, successors | Build an iterator without a new type |
Use these after the pipeline in the previous section is readable. They are the same pull model, not a new kind of loop.
fn main() {
let nums = [1, 2, 3, 4];
let mut windows = Vec::new();
for w in nums.windows(2) {
windows.push(w[0] + w[1]);
}
let names = ["ada", "lin"];
let scores = [10, 20];
let pairs: Vec<_> = names.iter().zip(scores).collect();
let (even, odd): (Vec<_>, Vec<_>) = nums.iter().partition(|n| *n % 2 == 0);
println!("{:?} {} {}", windows, pairs.len(), even.len() + odd.len());
}