Rust Notes: Closures and Iterators
A concise reference to Rust closures and iterators.
Rust Closures and Iterators
Closures and iterators are two important features of idiomatic Rust.
A closure is an anonymous function that can capture variables from its surrounding environment.
An iterator represents a sequence of values and produces those values one at a time.
They are commonly used together:
1
2
3
4
5
let result: Vec<_> = numbers
.iter()
.filter(|x| **x > 10)
.map(|x| x * 2)
.collect();
The closure provides the behavior, the iterator provides the sequence, and the consumer drives the computation.
1. Closures
A closure is an anonymous function.
Basic syntax
1
|parameters| expression
Examples:
1
2
3
4
5
6
7
8
9
10
11
let add = |x, y| x + y;
let square = |x: i32| x * x;
let add = |x: i32, y: i32| -> i32 {
x + y
};
let hello = || {
println!("Hello");
};
A closure can contain multiple statements:
1
2
3
4
let calculate = |x: i32| {
let y = x * 2;
y + 10
};
The last expression is the return value.
2. Closure vs Function
A normal function:
1
2
3
fn add(x: i32, y: i32) -> i32 {
x + y
}
A closure:
1
let add = |x: i32, y: i32| x + y;
The important difference is that closures can capture variables from their surrounding environment.
1
2
3
4
5
let multiplier = 10;
let multiply = |x| x * multiplier;
println!("{}", multiply(5));
A normal function cannot automatically access multiplier.
3. Closure Capture
Closures can capture variables by:
- immutable borrow
- mutable borrow
- ownership
Immutable borrow
1
2
3
4
5
let name = String::from("Amir");
let print_name = || {
println!("{name}");
};
The closure only reads name, so it can capture it by shared reference.
Mutable borrow
1
2
3
4
5
6
7
8
9
10
let mut counter = 0;
let mut increment = || {
counter += 1;
};
increment();
increment();
println!("{counter}");
The closure captures counter mutably.
The closure variable itself must be mutable:
1
let mut increment = ...
Move / ownership
1
2
3
4
5
6
7
let name = String::from("Amir");
let consume = || {
drop(name);
};
consume();
The closure moves name into its environment and then consumes it when called.
4. The move Keyword
The move keyword forces a closure to capture variables by value.
1
2
3
4
5
let name = String::from("Amir");
let print_name = move || {
println!("{name}");
};
Without move, Rust chooses the capture mode based on how the variable is used.
With move, ownership of the captured value is transferred to the closure.
This is especially important for threads:
1
2
3
4
5
6
7
use std::thread;
let data = String::from("hello");
thread::spawn(move || {
println!("{data}");
});
The spawned thread owns data.
5. move Does Not Mean FnOnce
This is an important distinction.
move describes how a closure captures variables.
Fn, FnMut, and FnOnce describe how the closure can be called.
For example:
1
2
3
4
5
6
7
8
let name = String::from("Amir");
let print_name = move || {
println!("{name}");
};
print_name();
print_name();
The closure owns name, but it only reads it.
Therefore it can still implement Fn.
The distinction is:
1
2
3
4
5
move
capture mode
Fn / FnMut / FnOnce
calling behavior
6. Fn, FnMut, and FnOnce
Rust has three closure-call traits:
1
2
3
Fn
FnMut
FnOnce
Their relationship is:
1
2
3
4
5
6
7
Fn
|
v
FnMut
|
v
FnOnce
Therefore:
1
2
Fn can be used where FnMut is required
FnMut can be used where FnOnce is required
Every closure implements FnOnce.
Some closures also implement FnMut.
Some closures also implement Fn.
7. Fn
A closure implements Fn when it can be called repeatedly without mutating or consuming captured values.
1
2
3
4
5
6
7
8
let name = String::from("Amir");
let print_name = || {
println!("{name}");
};
print_name();
print_name();
A function can require an Fn closure:
1
2
3
4
5
6
fn execute<F>(f: F)
where
F: Fn(),
{
f();
}
Or:
1
2
3
fn execute(f: impl Fn()) {
f();
}
8. FnMut
FnMut is used when the closure can be called repeatedly and may mutate captured state.
1
2
3
4
5
6
7
8
9
10
11
let mut counter = 0;
let mut increment = || {
counter += 1;
};
increment();
increment();
increment();
println!("{counter}");
A function accepting FnMut:
1
2
3
4
5
6
7
fn execute<F>(mut f: F)
where
F: FnMut(),
{
f();
f();
}
Or:
1
2
3
4
fn execute(mut f: impl FnMut()) {
f();
f();
}
9. FnOnce
FnOnce is used when calling the closure may consume captured values.
1
2
3
4
5
6
7
let name = String::from("Amir");
let consume = || {
drop(name);
};
consume();
The closure consumes name, so it cannot normally be called again.
A function accepting FnOnce:
1
2
3
4
5
6
fn execute<F>(f: F)
where
F: FnOnce(),
{
f();
}
Or:
1
2
3
fn execute(f: impl FnOnce()) {
f();
}
10. Choosing the Closure Trait
Use Fn when:
1
2
the closure can be called repeatedly
and does not mutate or consume captured state
Use FnMut when:
1
2
the closure can be called repeatedly
and may mutate captured state
Use FnOnce when:
1
2
the closure may consume captured state
or only needs to be called once
Examples:
1
2
3
fn run_once(f: impl FnOnce()) {
f();
}
1
2
3
4
fn run_many(mut f: impl FnMut()) {
f();
f();
}
1
2
3
4
fn run_read_only(f: impl Fn()) {
f();
f();
}
11. Closure Trait Syntax
Closure traits can be used with generic bounds:
1
F: Fn() -> i32
1
F: Fn(i32) -> i32
1
F: Fn(i32, i32) -> i32
1
F: Fn(&str) -> bool
The same syntax applies to:
1
FnMut(...)
and:
1
FnOnce(...)
Example:
1
2
3
4
5
6
fn transform<F>(value: i32, f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(value)
}
12. impl Fn Syntax
Instead of:
1
2
3
4
5
6
fn execute<F>(f: F)
where
F: Fn(),
{
f();
}
you can write:
1
2
3
fn execute(f: impl Fn()) {
f();
}
Similarly:
1
2
3
fn execute(f: impl FnMut()) {
// ...
}
1
2
3
fn execute(f: impl FnOnce()) {
// ...
}
With parameters and return values:
1
2
3
4
5
6
fn transform(
value: i32,
f: impl Fn(i32) -> i32,
) -> i32 {
f(value)
}
13. Returning a Closure
Closure types are anonymous, so impl Fn is commonly used when returning closures.
1
2
3
fn create_adder() -> impl Fn(i32) -> i32 {
|x| x + 10
}
Usage:
1
2
3
let add_ten = create_adder();
println!("{}", add_ten(5));
A returned closure can capture values:
1
2
3
fn create_adder(value: i32) -> impl Fn(i32) -> i32 {
move |x| x + value
}
The move makes the returned closure own value.
14. Closure Trait Objects
Closures can also be stored behind trait objects.
1
2
let f: Box<dyn Fn(i32) -> i32> =
Box::new(|x| x + 1);
Other forms:
1
&dyn Fn(i32) -> i32
1
Box<dyn Fn(i32) -> i32>
1
Arc<dyn Fn(i32) -> i32 + Send + Sync>
This uses dynamic dispatch.
By contrast:
1
impl Fn(i32) -> i32
normally uses static dispatch.
15. Function Pointers
A function pointer has the syntax:
1
fn(i32) -> i32
Example:
1
2
3
4
5
fn add_one(x: i32) -> i32 {
x + 1
}
let f: fn(i32) -> i32 = add_one;
A non-capturing closure can also become a function pointer:
1
let f: fn(i32) -> i32 = |x| x + 1;
A capturing closure generally cannot become a plain function pointer because a function pointer has no environment in which to store captured variables.
Iterators
16. The Iterator Trait
The core iterator abstraction is the Iterator trait:
1
2
3
4
5
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
The most important method is:
1
next()
It returns:
1
Some(value)
when another value exists, or:
1
None
when iteration is finished.
Example:
1
2
3
4
5
6
7
8
let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());
Output:
1
2
3
4
Some(1)
Some(2)
Some(3)
None
17. for Loops and Iterators
A for loop uses the iterator protocol internally.
1
2
3
for value in collection {
println!("{value}");
}
Conceptually, this is similar to:
1
2
3
4
5
6
7
8
9
10
let mut iterator = collection.into_iter();
loop {
match iterator.next() {
Some(value) => {
println!("{value}");
}
None => break,
}
}
Therefore, understanding Iterator::next() explains how for loops work.
18. iter()
iter() creates an iterator that yields immutable references.
1
2
3
4
5
6
7
let numbers = vec![1, 2, 3];
for number in numbers.iter() {
println!("{number}");
}
println!("{numbers:?}");
The vector still owns its elements.
Conceptually:
1
2
3
4
5
Vec<T>
|
+---- &T
+---- &T
+---- &T
19. iter_mut()
iter_mut() yields mutable references.
1
2
3
4
5
6
7
let mut numbers = vec![1, 2, 3];
for number in numbers.iter_mut() {
*number *= 2;
}
println!("{numbers:?}");
Conceptually:
1
2
3
4
5
Vec<T>
|
+---- &mut T
+---- &mut T
+---- &mut T
20. into_iter()
into_iter() consumes the collection and yields owned values.
1
2
3
4
5
let numbers = vec![1, 2, 3];
for number in numbers.into_iter() {
println!("{number}");
}
After this, numbers can no longer be used.
Conceptually:
1
2
3
4
5
6
7
Vec<T>
|
+---- T
+---- T
+---- T
Vec<T> is consumed
The basic rule is:
1
2
3
4
5
6
7
8
iter()
immutable borrow
iter_mut()
mutable borrow
into_iter()
ownership
21. Iterator Item Type
The Iterator trait has an associated type:
1
2
3
4
5
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
For:
1
2
3
let numbers = vec![1, 2, 3];
let iter = numbers.iter();
the Item type is:
1
&i32
For:
1
let iter = numbers.into_iter();
the Item type is:
1
i32
This is one reason iter(), iter_mut(), and into_iter() behave differently.
22. Lazy Iterators
Iterator adapters are lazy.
Consider:
1
2
3
4
5
6
7
8
let numbers = vec![1, 2, 3];
let result = numbers
.iter()
.map(|x| {
println!("processing {x}");
x * 2
});
The map() operation has not processed all values yet.
It has created another iterator.
The computation happens when something consumes the iterator:
1
2
3
4
5
6
7
let result: Vec<_> = numbers
.iter()
.map(|x| {
println!("processing {x}");
x * 2
})
.collect();
A useful mental model:
1
2
3
4
5
6
7
8
9
iterator adapters
|
| describe computation
v
consumer
|
| drives iterator
v
actual computation
23. Iterator Adapters
Iterator adapters usually return another iterator.
Common adapters include:
1
2
3
4
5
6
7
8
9
10
11
12
13
map
filter
filter_map
enumerate
zip
take
skip
chain
rev
peekable
flat_map
flatten
inspect
Example:
1
2
3
4
let iter = numbers
.iter()
.filter(|x| **x > 10)
.map(|x| x * 2);
The result is still an iterator.
24. map
map transforms every item.
1
2
3
4
5
6
let numbers = vec![1, 2, 3];
let result: Vec<_> = numbers
.iter()
.map(|x| x * 2)
.collect();
Result:
1
[2, 4, 6]
The closure:
1
|x| x * 2
defines the transformation.
25. filter
filter keeps items for which the closure returns true.
1
2
3
4
5
6
let numbers = vec![1, 2, 3, 4, 5];
let result: Vec<_> = numbers
.iter()
.filter(|x| **x % 2 == 0)
.collect();
Result:
1
[2, 4]
26. filter_map
filter_map combines filtering and mapping.
1
2
3
4
5
6
let values = vec!["1", "hello", "2"];
let numbers: Vec<i32> = values
.iter()
.filter_map(|value| value.parse().ok())
.collect();
Result:
1
[1, 2]
The closure returns:
1
Some(value)
to keep a transformed value, or:
1
None
to discard it.
27. enumerate
enumerate() adds an index.
1
2
3
4
5
let names = vec!["Alice", "Bob", "Charlie"];
for (index, name) in names.iter().enumerate() {
println!("{index}: {name}");
}
Output:
1
2
3
0: Alice
1: Bob
2: Charlie
28. zip
zip combines two iterators.
1
2
3
4
5
6
7
let names = vec!["Alice", "Bob"];
let ages = vec![20, 30];
let result: Vec<_> = names
.iter()
.zip(ages.iter())
.collect();
Result:
1
2
3
4
[
("Alice", 20),
("Bob", 30),
]
Iteration stops when either iterator ends.
29. take
take limits the number of elements.
1
2
3
4
5
let numbers = 1..100;
let result: Vec<_> = numbers
.take(5)
.collect();
Result:
1
[1, 2, 3, 4, 5]
30. skip
skip ignores the first elements.
1
2
3
4
5
let numbers = 1..6;
let result: Vec<_> = numbers
.skip(2)
.collect();
Result:
1
[3, 4, 5]
31. chain
chain joins two iterators.
1
2
3
4
5
6
7
let a = vec![1, 2];
let b = vec![3, 4];
let result: Vec<_> = a
.into_iter()
.chain(b.into_iter())
.collect();
Result:
1
[1, 2, 3, 4]
32. rev
rev() reverses a double-ended iterator.
1
2
3
4
5
6
let numbers = vec![1, 2, 3];
let result: Vec<_> = numbers
.into_iter()
.rev()
.collect();
Result:
1
[3, 2, 1]
33. flatten
flatten removes one level of nesting.
1
2
3
4
5
6
7
8
9
let values = vec![
vec![1, 2],
vec![3, 4],
];
let result: Vec<_> = values
.into_iter()
.flatten()
.collect();
Result:
1
[1, 2, 3, 4]
34. flat_map
flat_map maps and then flattens.
1
2
3
4
5
6
let words = vec!["hello", "world"];
let chars: Vec<_> = words
.into_iter()
.flat_map(|word| word.chars())
.collect();
Result:
1
['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
35. inspect
inspect is useful for debugging an iterator chain.
1
2
3
4
5
6
let result: Vec<_> = numbers
.iter()
.inspect(|x| println!("before: {x}"))
.map(|x| x * 2)
.inspect(|x| println!("after: {x}"))
.collect();
It does not change the values.
36. Consumers
Consumers drive an iterator and produce a final result or side effect.
Common consumers include:
1
2
3
4
5
6
7
8
9
10
11
12
13
collect
sum
product
count
fold
reduce
find
position
any
all
for_each
nth
last
37. collect
collect() converts an iterator into a collection.
1
2
3
let numbers = 1..5;
let result: Vec<i32> = numbers.collect();
Type inference can also determine the target type:
1
let result: Vec<_> = (1..5).collect();
You can explicitly specify the collection:
1
let result = (1..5).collect::<Vec<_>>();
38. sum
1
let total: i32 = (1..5).sum();
Result:
1
10
39. count
1
let count = (1..10).count();
Result:
1
9
40. fold
fold accumulates values.
1
2
3
let sum = (1..5).fold(0, |acc, value| {
acc + value
});
Result:
1
10
The first argument is the initial accumulator:
1
0
The closure receives:
1
2
accumulator
current item
and returns the next accumulator.
A shorter version:
1
let sum = (1..5).fold(0, |acc, x| acc + x);
41. find
find returns the first element matching a condition.
1
2
let result = (1..10)
.find(|x| *x > 5);
Result:
1
Some(6)
If nothing matches:
1
None
42. any
any returns true if at least one item matches.
1
2
let result = (1..10)
.any(|x| x > 8);
Result:
1
true
43. all
all returns true if every item matches.
1
2
let result = (1..10)
.all(|x| *x > 0);
Result:
1
true
44. for_each
for_each executes a closure for every item.
1
2
3
(1..5).for_each(|x| {
println!("{x}");
});
It is similar to:
1
2
3
for x in 1..5 {
println!("{x}");
}
45. nth
nth(n) returns the element at index n.
1
let result = (10..20).nth(3);
Result:
1
Some(13)
Note that nth() consumes all preceding iterator elements.
46. last
last() returns the final element.
1
let result = (1..5).last();
Result:
1
Some(4)
The iterator must be consumed to determine the final element.
Iterator Chains
47. Combining Adapters and Consumers
Iterator chains are one of the most common patterns in Rust.
1
2
3
4
5
6
7
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<_> = numbers
.iter()
.filter(|x| **x % 2 == 0)
.map(|x| x * 10)
.collect();
The flow is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
numbers
|
v
iter()
|
v
filter()
|
v
map()
|
v
collect()
|
v
Vec
The closures define what happens to each element.
The iterator controls when each element is requested.
48. Why Iterators Are Lazy
Consider:
1
2
3
let result = (1..100)
.filter(|x| x % 2 == 0)
.map(|x| x * 10);
Nothing has necessarily been computed yet.
result is an iterator.
When we call:
1
result.next()
the iterator starts doing the work necessary to produce one value.
For example:
1
2
3
4
5
6
7
8
next()
|
v
1 -> rejected
2 -> accepted
|
v
20
The iterator does not need to process all 100 values first.
This allows iterator chains to process values incrementally.
Iterator Ownership
49. iter, iter_mut, and into_iter
These three methods are fundamental.
| Method | Yields | Collection |
|---|---|---|
iter() | &T | borrowed |
iter_mut() | &mut T | mutably borrowed |
into_iter() | T | consumed |
Example:
1
2
3
4
5
6
7
let values = vec![String::from("a"), String::from("b")];
for value in values.iter() {
println!("{value}");
}
println!("{values:?}");
The vector remains available.
With into_iter():
1
2
3
4
5
let values = vec![String::from("a"), String::from("b")];
for value in values.into_iter() {
println!("{value}");
}
The vector is consumed.
Implementing Your Own Iterator
50. Custom Iterator
You can implement Iterator for your own type.
1
2
3
4
struct Counter {
current: u32,
max: u32,
}
Implement Iterator:
1
2
3
4
5
6
7
8
9
10
11
12
13
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
let value = self.current;
self.current += 1;
Some(value)
} else {
None
}
}
}
Usage:
1
2
3
4
5
6
7
8
let counter = Counter {
current: 0,
max: 5,
};
for value in counter {
println!("{value}");
}
Output:
1
2
3
4
5
0
1
2
3
4
The key idea is that the iterator controls its state through &mut self.
Iterators and Closures Together
51. The Common Rust Pattern
A very common Rust pattern is:
1
2
3
4
5
collection
.iter()
.filter(...)
.map(...)
.collect()
For example:
1
2
3
4
5
6
7
8
9
10
11
let users = vec![
("Alice", 20),
("Bob", 15),
("Charlie", 30),
];
let names: Vec<_> = users
.iter()
.filter(|(_, age)| *age >= 18)
.map(|(name, _)| *name)
.collect();
Here:
1
2
3
4
5
6
7
8
9
10
11
iter()
creates the iterator
filter()
uses a closure to decide what stays
map()
uses a closure to transform values
collect()
consumes the iterator
Iterator vs Collection
A collection stores values:
1
Vec<T>
An iterator describes how to obtain values:
1
Iterator<Item = T>
Conceptually:
1
2
3
4
5
6
7
8
9
10
11
Collection
|
| owns/stores values
v
Vec<T>
Iterator
|
| produces values
v
Item
An iterator does not necessarily store all the values.
For example:
1
let numbers = 1..1_000_000_000;
The range iterator does not need to store one billion integers.
It can produce them one at a time.
Iterator Adaptors vs Consumers
A useful distinction:
Adapters
Adapters return another iterator.
Examples:
1
2
3
4
5
6
7
8
9
10
map()
filter()
take()
skip()
zip()
enumerate()
chain()
rev()
flatten()
flat_map()
Example:
1
2
3
4
let iter = numbers
.iter()
.filter(...)
.map(...);
Still an iterator.
Consumers
Consumers finish the iteration.
Examples:
1
2
3
4
5
6
7
8
collect()
sum()
count()
fold()
find()
any()
all()
for_each()
Example:
1
2
3
4
5
let result = numbers
.iter()
.filter(...)
.map(...)
.collect();
collect() consumes the iterator.
Static Dispatch with Closures and Iterators
Generic closures and iterators commonly use static dispatch.
For example:
1
2
3
4
5
6
7
8
9
fn transform<I, F>(iter: I, f: F)
where
I: Iterator<Item = i32>,
F: Fn(i32) -> i32,
{
for value in iter {
println!("{}", f(value));
}
}
The compiler knows the concrete types of:
1
2
I
F
and can generate specialized code.
This can allow inlining and optimization.
Dynamic Dispatch with Closure Trait Objects
You can also use dynamic dispatch:
1
2
3
4
let functions: Vec<Box<dyn Fn(i32) -> i32>> = vec![
Box::new(|x| x + 1),
Box::new(|x| x * 2),
];
Now all closures are represented through:
1
dyn Fn(i32) -> i32
This is useful when different closure types need to be stored together.
Remember that every closure expression has its own anonymous type:
1
2
let a = |x| x + 1;
let b = |x| x * 2;
Even though both have similar signatures, their concrete types are different.
Therefore:
1
Vec<_>
cannot directly contain both closure types.
Trait objects solve this:
1
Vec<Box<dyn Fn(i32) -> i32>>
Closures, Iterators, and Ownership
Closures inside iterator chains follow normal Rust ownership rules.
For example:
1
2
3
4
5
6
7
8
9
let names = vec![
String::from("Alice"),
String::from("Bob"),
];
let lengths: Vec<_> = names
.iter()
.map(|name| name.len())
.collect();
iter() yields:
1
&String
so the closure receives references.
If we consume the vector:
1
2
3
4
5
6
7
8
9
let names = vec![
String::from("Alice"),
String::from("Bob"),
];
let lengths: Vec<_> = names
.into_iter()
.map(|name| name.len())
.collect();
the closure receives owned String values.
Closures and move in Iterator Chains
A move closure can take ownership of external variables.
1
2
3
4
5
let multiplier = 10;
let result: Vec<_> = (1..5)
.map(move |x| x * multiplier)
.collect();
The closure owns multiplier.
This is particularly useful when a closure needs to outlive its current scope or when it is passed to another thread/task.
Closures in Multithreaded Code
Closures are heavily used with threads:
1
2
3
4
5
6
7
8
9
10
11
use std::thread;
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
for value in data {
println!("{value}");
}
});
handle.join().unwrap();
The move transfers ownership of data into the closure.
The closure must satisfy the requirements imposed by thread::spawn, including appropriate lifetime and thread-safety bounds.
Closures in Async Rust
Closures and async blocks are different language constructs, but they can work together.
For example:
1
2
3
let task = async move {
println!("{data}");
};
An async move block captures variables by value and produces a Future.
This is conceptually similar to:
1
2
3
move || {
// closure body
}
but an async block produces a future rather than a callable closure.
Important Mental Model
A useful way to think about closures is:
1
2
3
4
5
Closure
=
function
+
captured environment
For example:
1
2
3
let multiplier = 10;
let multiply = |x| x * multiplier;
Conceptually:
1
2
3
4
5
6
7
multiply
+-------------------+
| multiplier: 10 |
| |
| call(x): |
| x * multiplier |
+-------------------+
A useful way to think about iterators is:
1
2
3
4
5
Iterator
=
state
+
next() operation
Conceptually:
1
2
3
4
5
6
7
8
Iterator
+-------------------+
| current state |
| |
| next() |
| -> Some(value) |
| -> None |
+-------------------+
Common Syntax Cheat Sheet
Closures
1
|| {}
1
|x| x + 1
1
|x: i32| x + 1
1
2
3
|x: i32| -> i32 {
x + 1
}
1
move |x| x + value
Closure bounds
1
F: Fn()
1
F: Fn(i32) -> i32
1
F: FnMut()
1
F: FnOnce()
1
impl Fn()
1
impl FnMut()
1
impl FnOnce()
Closure trait objects
1
dyn Fn()
1
Box<dyn Fn()>
1
Arc<dyn Fn() + Send + Sync>
Function pointers
1
fn(i32) -> i32
Iterators
1
collection.iter()
1
collection.iter_mut()
1
collection.into_iter()
1
iterator.next()
Adapters
1
.map(...)
1
.filter(...)
1
.filter_map(...)
1
.enumerate()
1
.zip(...)
1
.take(...)
1
.skip(...)
1
.chain(...)
1
.rev()
1
.flatten()
1
.flat_map(...)
1
.inspect(...)
Consumers
1
.collect()
1
.sum()
1
.count()
1
.fold(initial, closure)
1
.find(closure)
1
.any(closure)
1
.all(closure)
1
.for_each(closure)
1
.nth(index)
1
.last()
Key Takeaways
- A closure is an anonymous function that can capture its environment.
- Closures can capture by immutable reference, mutable reference, or ownership.
moveforces a closure to capture values by ownership.moveandFnOnceare different concepts.Fnmeans the closure can be called without mutating or consuming its environment.FnMutallows mutation of captured state.FnOnceallows captured values to be consumed.- Every closure implements
FnOnce. iter()yields&T.iter_mut()yields&mut T.into_iter()yields ownedTvalues and consumes the collection.Iterator::next()is the core iterator operation.- Iterator adapters such as
mapandfilterare lazy. - Consumers such as
collect,sum, andfolddrive the iterator. - Iterator chains are a combination of iterator state and closures.
- Generic closures and iterators normally use static dispatch.
dyn Fn,Box<dyn Fn>, and similar types provide dynamic dispatch.- Closures and iterators work closely with Rust’s ownership and borrowing system.
- Understanding
Fn/FnMut/FnOnceanditer/iter_mut/into_iteris essential for idiomatic Rust.