On this page
Closures and iterators: functional programming in Rust
Closures: anonymous functions that capture the environment
Closures are anonymous functions that can capture variables from the surrounding environment. They are fundamental to functional programming in Rust and are used extensively with iterators.
fn main() {
// Regular function
fn square(n: i32) -> i32 { n * n }
// Equivalent closure (with type inference)
let square_cl = |n: i32| -> i32 { n * n };
// Closure with full inference
let square_inf = |n| n * n;
// Single-expression closure (no curly braces needed)
let double = |n| n * 2;
println!("{}", square(5));
println!("{}", square_cl(5));
println!("{}", square_inf(5_i32));
println!("{}", double(7_i32));
}Capturing the environment
Unlike functions, closures can capture variables from the environment in three ways:
fn main() {
let x = 10;
let y = String::from("hello");
// Capture by reference (&T) — reads the value
let read_x = || println!("x = {x}");
read_x();
read_x(); // Can be called multiple times
println!("x is still: {x}"); // x is still available
// Capture by mutable reference (&mut T)
let mut counter = 0;
let mut increment = || {
counter += 1;
println!("Counter: {counter}");
};
increment();
increment();
// You can't use counter here while the closure exists
drop(increment);
println!("Final counter: {counter}"); // Now you can
// Capture by move (move keyword)
let move_y = move || println!("y captured: {y}");
// y is no longer available here
move_y();
}The Fn, FnMut, and FnOnce traits
Closures in Rust implement one (or more) of these three traits:
| Trait | Description | Can be called |
|---|---|---|
FnOnce |
Consumes captured environment | Only once |
FnMut |
Modifies captured environment | Multiple times |
Fn |
Reads captured environment | Multiple times |
All closures implement FnOnce. If they don't move their captures, they also implement FnMut. If they don't modify their captures, they also implement Fn.
fn apply_once<F: FnOnce() -> String>(f: F) -> String {
f() // Can only be called once
}
fn apply_n_times<F: FnMut(i32) -> i32>(mut f: F, value: i32, n: u32) -> i32 {
let mut result = value;
for _ in 0..n {
result = f(result);
}
result
}
fn apply_to_each<F: Fn(i32) -> i32>(f: F, values: &[i32]) -> Vec<i32> {
values.iter().map(|&v| f(v)).collect()
}
fn main() {
let name = String::from("Rust");
let greeting = move || format!("Hello, {name}!"); // FnOnce (moves name)
println!("{}", apply_once(greeting));
let result = apply_n_times(|n| n * 2, 1, 10);
println!("2^10 = {result}");
let triple = |n| n * 3;
let tripled = apply_to_each(triple, &[1, 2, 3, 4, 5]);
println!("{:?}", tripled);
}The Iterator trait
The Iterator trait is the interface all iterators implement:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// Methods with default implementations (many more exist)
fn map<B, F: FnMut(Self::Item) -> B>(self, f: F) -> Map<Self, F> { ... }
fn filter<P: FnMut(&Self::Item) -> bool>(self, predicate: P) -> Filter<Self, P> { ... }
// ...
}You can implement your own iterator:
struct CountUpTo {
current: u32,
max: u32,
}
impl CountUpTo {
fn new(max: u32) -> Self {
CountUpTo { current: 0, max }
}
}
impl Iterator for CountUpTo {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.current < self.max {
self.current += 1;
Some(self.current)
} else {
None
}
}
}
fn main() {
let counter = CountUpTo::new(5);
// As an Iterator, you get ALL iterator methods for free!
let sum: u32 = counter.sum();
println!("Sum 1 to 5: {sum}");
// Chaining
let even_squares: Vec<u32> = CountUpTo::new(10)
.filter(|n| n % 2 == 0)
.map(|n| n * n)
.collect();
println!("{:?}", even_squares);
}Iterator adapters
Adapters transform one iterator into another (they are lazy):
fn main() {
let words = vec!["hello", "world", "rust", "is", "great"];
// map: transform each element
let lengths: Vec<usize> = words.iter().map(|s| s.len()).collect();
println!("{:?}", lengths);
// filter: keep elements that match condition
let long_words: Vec<&&str> = words.iter().filter(|s| s.len() > 4).collect();
println!("{:?}", long_words);
// enumerate: add index
for (i, word) in words.iter().enumerate() {
println!("[{i}] {word}");
}
// zip: combine two iterators in parallel
let numbers = [1, 2, 3, 4, 5];
let pairs: Vec<(&&str, &i32)> = words.iter().zip(numbers.iter()).collect();
println!("{:?}", pairs);
// take and skip
let first_two: Vec<&&str> = words.iter().take(2).collect();
let skip_first: Vec<&&str> = words.iter().skip(1).collect();
println!("First 2: {:?}", first_two);
println!("Skip first: {:?}", skip_first);
// chain: concatenate iterators
let a = [1, 2, 3];
let b = [4, 5, 6];
let chained: Vec<&i32> = a.iter().chain(b.iter()).collect();
println!("{:?}", chained);
// flat_map: map and flatten
let sentences = vec!["hello world", "rust is great"];
let all_words: Vec<&str> = sentences.iter()
.flat_map(|s| s.split_whitespace())
.collect();
println!("{:?}", all_words);
// windows and chunks
let data = [1, 2, 3, 4, 5, 6];
let windows: Vec<&[i32]> = data.windows(3).collect();
println!("Windows of 3: {:?}", windows);
let chunks: Vec<&[i32]> = data.chunks(2).collect();
println!("Chunks of 2: {:?}", chunks);
}Iterator consumers
Consumers terminate the chain and produce a result:
fn main() {
let numbers: Vec<i32> = (1..=10).collect();
// collect: build a collection
let squares: Vec<i32> = numbers.iter().map(|&n| n * n).collect();
// sum and product
let sum: i32 = numbers.iter().sum();
let product: i64 = numbers.iter().map(|&n| n as i64).product();
println!("Sum: {sum}, Product: {product}");
// fold: custom accumulator
let manual_sum = numbers.iter().fold(0_i32, |acc, &n| acc + n);
let max_manual = numbers.iter().fold(i32::MIN, |acc, &n| acc.max(n));
println!("Sum: {manual_sum}, Max: {max_manual}");
// count, min, max
let evens = numbers.iter().filter(|&&n| n % 2 == 0).count();
println!("Evens: {evens}");
println!("Min: {:?}", numbers.iter().min());
println!("Max: {:?}", numbers.iter().max());
// any and all
let has_even = numbers.iter().any(|&n| n % 2 == 0);
let all_positive = numbers.iter().all(|&n| n > 0);
println!("Has even? {has_even}, All positive? {all_positive}");
// find and position
let first_even = numbers.iter().find(|&&n| n % 2 == 0);
let position = numbers.iter().position(|&n| n == 5);
println!("First even: {:?}, Position of 5: {:?}", first_even, position);
// for_each: side effects without collect
numbers.iter().filter(|&&n| n > 7).for_each(|n| print!("{n} "));
println!();
}Closures and iterators make Rust code concise, expressive, and efficient. In the next lesson we will see safe concurrency — one of the areas where Rust truly shines.
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Iterator chain: no intermediate copies
let result: Vec<String> = numbers
.iter()
.filter(|&&n| n % 2 == 0) // Only evens: 2,4,6,8,10
.map(|&n| n * n) // Square: 4,16,36,64,100
.filter(|&n| n > 20) // Only > 20: 36,64,100
.enumerate() // Add index: (0,36),(1,64),(2,100)
.map(|(i, n)| format!("[{i}] {n}"))
.collect();
println!("{:?}", result);
// fold: accumulate a value
let sum: i32 = numbers.iter().fold(0, |acc, &n| acc + n);
let product: i64 = numbers.iter().map(|&n| n as i64).product();
println!("Sum: {sum}, Product: {product}");
// Closure that captures from the environment
let threshold = 5;
let greater: Vec<i32> = numbers
.iter()
.filter(|&&n| n > threshold) // threshold captured
.cloned()
.collect();
println!("Greater than {threshold}: {:?}", greater);
}
Sign in to track your progress