Lesson 16

Closures & Iterators

Harness the power of closures and iterators for clean, expressive, and fast data processing.

Closures

Closures are anonymous functions that can capture variables from their surrounding scope. The syntax is concise: |x| x + 1 instead of fn add_one(x: i32) -> i32. Rust infers the parameter and return types from usage. Closures can capture values by reference, by mutable reference, or by taking ownership.

Iterators

Rust iterators are lazy -- they do nothing until you consume them. Call .iter() for immutable references, .iter_mut() for mutable references, or .into_iter() to take ownership. Then chain adaptors like .map(), .filter(), .enumerate(), and .zip() to build powerful pipelines.

Chaining and Collecting

The real beauty is chaining multiple operations together and finishing with a consumer like .collect(), .sum(), .count(), or .for_each(). These chains compile down to the same performance as hand-written loops -- Rust calls this zero-cost abstractions. Once you get comfortable with this style, you'll find it clearer than most loop-based code.

Try it

fn main() {
    // --- Closures ---
    println!("--- Closures ---");

    // Basic closure syntax
    let add_one = |x: i32| x + 1;
    let multiply = |x, y| x * y;
    println!("  add_one(5) = {}", add_one(5));
    println!("  multiply(3, 4) = {}", multiply(3, 4));

    // Closures capture variables from their environment
    let greeting = String::from("Hello");
    let greet = |name| format!("{}, {}!", greeting, name);
    println!("  {}", greet("Rustacean"));

    // Multi-line closure
    let classify = |n: i32| {
        if n > 0 { "positive" }
        else if n < 0 { "negative" }
        else { "zero" }
    };
    println!("  7 is {}", classify(7));
    println!("  -3 is {}", classify(-3));

    // --- Iterators ---
    println!("\n--- Iterator Basics ---");
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // map: transform each element
    let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
    println!("  Doubled: {:?}", doubled);

    // filter: keep elements matching a condition
    let evens: Vec<&i32> = numbers.iter().filter(|n| *n % 2 == 0).collect();
    println!("  Evens: {:?}", evens);

    // enumerate: get index and value
    let first_three: Vec<String> = numbers
        .iter()
        .enumerate()
        .take(3)
        .map(|(i, val)| format!("[{}]={}", i, val))
        .collect();
    println!("  First three indexed: {:?}", first_three);

    // --- Chaining ---
    println!("\n--- Chaining Operations ---");

    // Chain multiple operations together
    let result: Vec<i32> = numbers
        .iter()
        .filter(|&&n| n % 2 == 0)       // keep even numbers -> this is a &&i32 because of iter() + filter()
        .map(|n| n * n)                  // square them
        .collect();
    println!("  Even squares: {:?}", result);

    // Consuming iterators: sum, count, any, all
    let sum: i32 = numbers.iter().sum();
    let count = numbers.iter().filter(|n| **n > 5).count();
    let any_negative = numbers.iter().any(|n| *n < 0);
    let all_positive = numbers.iter().all(|n| *n > 0);
    println!("  Sum: {}", sum);
    println!("  Count > 5: {}", count);
    println!("  Any negative: {}", any_negative);
    println!("  All positive: {}", all_positive);

    // --- Practical Example ---
    println!("\n--- Practical: Processing Names ---");
    let names = vec!["alice", "bob", "charlie", "diana"];

    let formatted: Vec<String> = names
        .iter()
        .filter(|name| name.len() > 3)
        .map(|name| {
            let mut chars = name.chars();
            match chars.next() {
                Some(c) => c.to_uppercase().to_string() + chars.as_str(),
                None => String::new(),
            }
        })
        .collect();

    println!("  Long names, capitalized: {:?}", formatted);

    // fold: the most flexible iterator consumer
    let sentence = names
        .iter()
        .fold(String::new(), |acc, name| {
            if acc.is_empty() {
                name.to_string()
            } else {
                format!("{}, {}", acc, name)
            }
        });
    println!("  Joined: {}", sentence);
}

Challenge

Use iterators to find the sum of squares of even numbers from 1 to 10. Hint: start with (1..=10) and chain .filter(), .map(), and .sum().

Previous: Lifetimes

Next: String Types