On this page

Closures and iterators: functional programming in Rust

14 min read TextCh. 5 — Rust in Practice

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.

Iterators are lazy
Operations like map, filter, take, skip process nothing until you call a consumer (collect, for_each, sum, fold, etc.). This means you can build very long chains without overhead — the compiler fuses them into a single loop.
iter() vs into_iter() vs iter_mut()
iter() borrows elements (&T), iter_mut() borrows mutably (&mut T), into_iter() consumes the collection (T). For Vec<T>: .iter() yields &T, .iter_mut() yields &mut T, .into_iter() moves T out. When you use for x in &vec you use iter(), and for x in vec you use into_iter().
rust
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);
}