TechLead
Lesson 11 of 28
5 min read
Rust

Closures & Iterators

Learn Rust closures with Fn, FnMut, FnOnce traits, iterator adaptors like map, filter, fold, collect, and how to build custom iterators.

Closures

Closures are anonymous functions that capture variables from their enclosing scope. They're used extensively with iterators, callbacks, and anywhere you need a short inline function.

fn main() {
    // Closure syntax variations
    let add_one = |x: i32| -> i32 { x + 1 };  // Full annotation
    let add_two = |x| x + 2;                    // Type inferred
    let double = |x| x * 2;                     // Minimal

    println!("{} {} {}", add_one(5), add_two(5), double(5));

    // Multi-line closure
    let calculate = |a: i32, b: i32| {
        let sum = a + b;
        let product = a * b;
        (sum, product)
    };
    let (s, p) = calculate(3, 4);
    println!("Sum: {s}, Product: {p}");

    // Closures capture from the environment
    let name = String::from("Rust");
    let greeting = || println!("Hello, {name}!");
    greeting();
    greeting(); // Can call multiple times (immutable borrow)
    println!("Name still valid: {name}");

    // Mutable capture
    let mut count = 0;
    let mut increment = || {
        count += 1;
        count
    };
    println!("{}", increment()); // 1
    println!("{}", increment()); // 2

    // Move capture — takes ownership
    let data = vec![1, 2, 3];
    let owns_data = move || {
        println!("I own: {:?}", data);
    };
    owns_data();
    // println!("{:?}", data); // ERROR: data was moved
}

Fn, FnMut, FnOnce Traits

Closure Traits Hierarchy

  • FnOnce: Can be called once. Consumes captured variables. All closures implement this.
  • FnMut: Can be called multiple times. May mutate captured variables. Also implements FnOnce.
  • Fn: Can be called multiple times. Only reads captured variables. Also implements FnMut and FnOnce.
// Accepting closures as function parameters
fn apply_twice i32>(f: F, x: i32) -> i32 {
    f(f(x))
}

fn call_with_value(f: F) {
    f(String::from("hello")); // Can only call once
}

fn mutate_and_call(mut f: F, times: usize) {
    for _ in 0..times {
        f();
    }
}

// Returning closures
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

fn main() {
    let result = apply_twice(|x| x + 3, 7);
    println!("Applied twice: {result}"); // 13

    call_with_value(|s| println!("Got: {s}"));

    let mut total = 0;
    mutate_and_call(|| total += 1, 5);
    println!("Total: {total}"); // 5

    let add_5 = make_adder(5);
    println!("10 + 5 = {}", add_5(10));
}

Iterators

fn main() {
    let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // Iterator adaptors (lazy — nothing happens until consumed)
    let evens: Vec = v.iter()
        .filter(|&&x| x % 2 == 0)
        .cloned()
        .collect();
    println!("Evens: {:?}", evens);

    let doubled: Vec = v.iter()
        .map(|&x| x * 2)
        .collect();
    println!("Doubled: {:?}", doubled);

    // Chaining adaptors
    let result: Vec = v.iter()
        .filter(|&&x| x > 3)
        .map(|&x| x * x)
        .take(3)
        .collect();
    println!("Filtered, squared, taken 3: {:?}", result);

    // fold (like reduce)
    let sum: i32 = v.iter().sum();
    let product: i32 = v.iter().fold(1, |acc, &x| acc * x);
    println!("Sum: {sum}, Product: {product}");

    // More useful adaptors
    let zipped: Vec<(i32, &str)> = vec![1, 2, 3]
        .into_iter()
        .zip(vec!["a", "b", "c"])
        .collect();
    println!("Zipped: {:?}", zipped);

    let chained: Vec = vec![1, 2, 3]
        .into_iter()
        .chain(vec![4, 5, 6])
        .collect();
    println!("Chained: {:?}", chained);

    // enumerate, any, all, find, position
    let found = v.iter().find(|&&x| x > 5);
    let pos = v.iter().position(|&x| x == 7);
    let all_pos = v.iter().all(|&x| x > 0);
    let any_neg = v.iter().any(|&x| x < 0);
    println!("Found: {:?}, Pos: {:?}, All>0: {all_pos}, Any<0: {any_neg}", found, pos);
}

Custom Iterators

struct Fibonacci {
    a: u64,
    b: u64,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci { a: 0, b: 1 }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option {
        let result = self.a;
        let new_b = self.a + self.b;
        self.a = self.b;
        self.b = new_b;
        Some(result) // Infinite iterator
    }
}

fn main() {
    // Take first 10 Fibonacci numbers
    let fibs: Vec = Fibonacci::new().take(10).collect();
    println!("Fibonacci: {:?}", fibs);

    // Sum of first 20 Fibonacci numbers
    let sum: u64 = Fibonacci::new().take(20).sum();
    println!("Sum of first 20 fibs: {sum}");

    // First Fibonacci number over 1000
    let big = Fibonacci::new().find(|&n| n > 1000);
    println!("First fib > 1000: {:?}", big);
}

Key Takeaways

  • ✅ Closures capture environment variables and implement Fn, FnMut, or FnOnce
  • ✅ Use move to force ownership transfer into closures
  • ✅ Iterator adaptors (map, filter, fold) are lazy — they only execute when consumed
  • ✅ Chaining iterators is idiomatic Rust and compiles to efficient machine code
  • ✅ Implement the Iterator trait to create custom iteration logic

Continue Learning