1. rust
  2. /advanced
  3. /closures-iterators

Closures & Iterators

Closures and iterators are powerful functional programming features in Rust that enable expressive, efficient code. Closures are anonymous functions that can capture variables from their environment, while iterators provide lazy evaluation and composable data processing.

Closures

Closures are anonymous functions that can capture values from their surrounding scope. They provide a concise way to define small functions inline.

Basic Closure Syntax

fn main() {
    // Basic closure syntax
    let add_one = |x| x + 1;
    println!("5 + 1 = {}", add_one(5));
    
    // Closure with multiple parameters
    let add = |x, y| x + y;
    println!("3 + 4 = {}", add(3, 4));
    
    // Closure with explicit types
    let multiply: fn(i32, i32) -> i32 = |x, y| x * y;
    println!("3 * 4 = {}", multiply(3, 4));
    
    // Closure with block body
    let complex_calculation = |x: i32| {
        let doubled = x * 2;
        let squared = doubled * doubled;
        squared + 1
    };
    println!("Complex result: {}", complex_calculation(3));
    
    // Closure with no parameters
    let get_random = || 42;
    println!("Random number: {}", get_random());
}

Variable Capture

Closures can capture variables from their environment in three ways:

fn main() {
    let x = 10;
    let y = 20;
    let mut z = 30;
    
    // Capture by reference (immutable)
    let closure_ref = || println!("x = {}, y = {}", x, y);
    closure_ref();
    
    // Capture by mutable reference
    let mut closure_mut = || {
        z += 1;
        println!("z = {}", z);
    };
    closure_mut();
    closure_mut();
    
    // Capture by value (move)
    let closure_move = move || {
        println!("Moved values: x = {}, y = {}", x, y);
    };
    closure_move();
    
    // x and y are still accessible here because they were copied
    println!("Original x = {}, y = {}", x, y);
    // z is still accessible because it was borrowed mutably
    println!("Final z = {}", z);
}

Closure Traits

Closures implement one or more of three traits based on what they capture:

// FnOnce: Called at most once, takes ownership
fn call_once<F>(f: F) -> i32 
where
    F: FnOnce() -> i32,
{
    f()
}

// FnMut: Can be called multiple times, can mutate captured variables
fn call_multiple_times<F>(mut f: F) 
where
    F: FnMut() -> i32,
{
    println!("First call: {}", f());
    println!("Second call: {}", f());
}

// Fn: Can be called multiple times, immutable access to captured variables
fn call_many_times<F>(f: F) 
where
    F: Fn() -> i32,
{
    for i in 0..3 {
        println!("Call {}: {}", i + 1, f());
    }
}

fn main() {
    let x = 5;
    
    // Fn closure - immutable borrow
    let fn_closure = || x * 2;
    call_many_times(fn_closure);
    
    let mut counter = 0;
    
    // FnMut closure - mutable borrow
    let mut fn_mut_closure = || {
        counter += 1;
        counter
    };
    call_multiple_times(&mut fn_mut_closure);
    
    let data = vec![1, 2, 3];
    
    // FnOnce closure - takes ownership
    let fn_once_closure = move || {
        data.len() as i32
    };
    call_once(fn_once_closure);
    // data is no longer accessible here
}

Returning Closures

// Return closure using Box for dynamic dispatch
fn make_adder(x: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |y| x + y)
}

// Return closure using impl Fn for static dispatch
fn make_multiplier(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x * y
}

// Generic function that takes a closure factory
fn create_and_use<F, C>(factory: F, value: i32) -> i32
where
    F: FnOnce() -> C,
    C: Fn(i32) -> i32,
{
    let closure = factory();
    closure(value)
}

fn main() {
    let add_5 = make_adder(5);
    println!("10 + 5 = {}", add_5(10));
    
    let multiply_3 = make_multiplier(3);
    println!("7 * 3 = {}", multiply_3(7));
    
    let result = create_and_use(|| |x| x * x, 4);
    println!("4^2 = {}", result);
}

Iterators

Iterators provide a powerful way to process sequences of data lazily and efficiently.

Creating Iterators

fn main() {
    // From collections
    let vec = vec![1, 2, 3, 4, 5];
    let iter1 = vec.iter();        // Iterator over references
    let iter2 = vec.into_iter();   // Iterator over owned values
    // let iter3 = vec.iter_mut();    // Iterator over mutable references
    
    // From ranges
    let range_iter = 0..10;
    
    // Manual iterator creation
    let mut manual_iter = (0..5).into_iter();
    
    println!("Manual iteration:");
    while let Some(value) = manual_iter.next() {
        println!("  {}", value);
    }
    
    // Iterator from function
    let fibonacci = std::iter::successors(Some((0, 1)), |(a, b)| Some((*b, a + b)))
        .map(|(a, _)| a);
    
    println!("First 10 Fibonacci numbers:");
    for (i, fib) in fibonacci.take(10).enumerate() {
        println!("  F({}) = {}", i, fib);
    }
}

Iterator Adaptors

Iterator adaptors transform one iterator into another:

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // map: Transform each element
    let squared: Vec<i32> = numbers.iter()
        .map(|x| x * x)
        .collect();
    println!("Squared: {:?}", squared);
    
    // filter: Keep elements matching predicate
    let even: Vec<&i32> = numbers.iter()
        .filter(|&&x| x % 2 == 0)
        .collect();
    println!("Even numbers: {:?}", even);
    
    // enumerate: Add indices
    let with_indices: Vec<(usize, &i32)> = numbers.iter()
        .enumerate()
        .collect();
    println!("With indices: {:?}", with_indices);
    
    // zip: Combine with another iterator
    let letters = vec!['a', 'b', 'c', 'd', 'e'];
    let pairs: Vec<(&i32, &char)> = numbers.iter()
        .zip(letters.iter())
        .collect();
    println!("Pairs: {:?}", pairs);
    
    // take: Take first n elements
    let first_three: Vec<&i32> = numbers.iter()
        .take(3)
        .collect();
    println!("First three: {:?}", first_three);
    
    // skip: Skip first n elements
    let skip_three: Vec<&i32> = numbers.iter()
        .skip(3)
        .collect();
    println!("Skip three: {:?}", skip_three);
    
    // chain: Combine multiple iterators
    let more_numbers = vec![11, 12, 13];
    let chained: Vec<i32> = numbers.iter()
        .chain(more_numbers.iter())
        .cloned()
        .collect();
    println!("Chained: {:?}", chained);
}

Consuming Adaptors

Consuming adaptors consume the iterator and produce a final result:

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // collect: Consume into collection
    let doubled: Vec<i32> = numbers.iter()
        .map(|x| x * 2)
        .collect();
    println!("Doubled: {:?}", doubled);
    
    // reduce/fold: Combine all elements
    let sum = numbers.iter().sum::<i32>();
    println!("Sum: {}", sum);
    
    let product = numbers.iter().product::<i32>();
    println!("Product: {}", product);
    
    let custom_fold = numbers.iter().fold(0, |acc, x| acc + x * x);
    println!("Sum of squares: {}", custom_fold);
    
    // find: Find first matching element
    let found = numbers.iter().find(|&&x| x > 3);
    println!("First > 3: {:?}", found);
    
    // any/all: Test conditions
    let has_even = numbers.iter().any(|&x| x % 2 == 0);
    println!("Has even: {}", has_even);
    
    let all_positive = numbers.iter().all(|&x| x > 0);
    println!("All positive: {}", all_positive);
    
    // count: Count elements
    let count = numbers.iter().count();
    println!("Count: {}", count);
    
    // nth: Get element at position
    let third = numbers.iter().nth(2);
    println!("Third element: {:?}", third);
    
    // max/min
    let max = numbers.iter().max();
    let min = numbers.iter().min();
    println!("Max: {:?}, Min: {:?}", max, min);
}

Advanced Iterator Patterns

use std::collections::HashMap;

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
    city: String,
}

fn main() {
    let people = vec![
        Person { name: "Alice".to_string(), age: 30, city: "New York".to_string() },
        Person { name: "Bob".to_string(), age: 25, city: "San Francisco".to_string() },
        Person { name: "Charlie".to_string(), age: 35, city: "New York".to_string() },
        Person { name: "Diana".to_string(), age: 28, city: "Seattle".to_string() },
    ];
    
    // Group by city
    let by_city: HashMap<String, Vec<&Person>> = people.iter()
        .fold(HashMap::new(), |mut acc, person| {
            acc.entry(person.city.clone()).or_insert_with(Vec::new).push(person);
            acc
        });
    
    println!("Grouped by city:");
    for (city, residents) in &by_city {
        println!("  {}: {:?}", city, residents.iter().map(|p| &p.name).collect::<Vec<_>>());
    }
    
    // Find oldest person in each city
    let oldest_by_city: HashMap<String, &Person> = people.iter()
        .fold(HashMap::new(), |mut acc, person| {
            acc.entry(person.city.clone())
                .and_modify(|current| {
                    if person.age > current.age {
                        *current = person;
                    }
                })
                .or_insert(person);
            acc
        });
    
    println!("\nOldest by city:");
    for (city, person) in &oldest_by_city {
        println!("  {}: {} (age {})", city, person.name, person.age);
    }
    
    // Complex filtering and transformation
    let young_city_dwellers: Vec<String> = people.iter()
        .filter(|person| person.age < 30)
        .filter(|person| person.city != "Seattle")
        .map(|person| format!("{} from {}", person.name, person.city))
        .collect();
    
    println!("\nYoung city dwellers (not from Seattle): {:?}", young_city_dwellers);
}

Custom Iterators

struct Counter {
    current: usize,
    max: usize,
}

impl Counter {
    fn new(max: usize) -> Counter {
        Counter { current: 0, max }
    }
}

impl Iterator for Counter {
    type Item = usize;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.current < self.max {
            let current = self.current;
            self.current += 1;
            Some(current)
        } else {
            None
        }
    }
}

// Iterator that yields Fibonacci numbers
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<Self::Item> {
        let next = self.a;
        self.a = self.b;
        self.b = next + self.b;
        Some(next)
    }
}

fn main() {
    // Using custom counter
    let counter = Counter::new(5);
    let sum: usize = counter.sum();
    println!("Sum of 0..5: {}", sum);
    
    // Using Fibonacci iterator
    let fibonacci_sum: u64 = Fibonacci::new()
        .take(10)
        .sum();
    println!("Sum of first 10 Fibonacci numbers: {}", fibonacci_sum);
    
    // Combining custom iterators
    let combined: Vec<(usize, u64)> = Counter::new(8)
        .zip(Fibonacci::new())
        .collect();
    println!("Counter + Fibonacci: {:?}", combined);
}

Functional Programming Patterns

Higher-Order Functions

// Function that takes a closure
fn apply_operation<F>(numbers: Vec<i32>, operation: F) -> Vec<i32>
where
    F: Fn(i32) -> i32,
{
    numbers.into_iter().map(operation).collect()
}

// Function that returns a closure
fn make_transformer(factor: i32) -> impl Fn(i32) -> i32 {
    move |x| x * factor
}

// Function composition
fn compose<F, G, A, B, C>(f: F, g: G) -> impl Fn(A) -> C
where
    F: Fn(A) -> B,
    G: Fn(B) -> C,
{
    move |x| g(f(x))
}

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    
    // Using higher-order function
    let doubled = apply_operation(numbers.clone(), |x| x * 2);
    println!("Doubled: {:?}", doubled);
    
    // Using closure factory
    let triple = make_transformer(3);
    let tripled = apply_operation(numbers.clone(), triple);
    println!("Tripled: {:?}", tripled);
    
    // Function composition
    let add_one = |x| x + 1;
    let square = |x| x * x;
    let add_one_then_square = compose(add_one, square);
    
    let result: Vec<i32> = numbers.into_iter()
        .map(add_one_then_square)
        .collect();
    println!("Add 1 then square: {:?}", result);
}

Lazy Evaluation

fn main() {
    println!("Lazy evaluation example:");
    
    // This doesn't actually compute anything yet
    let lazy_computation = (0..1_000_000)
        .map(|x| {
            println!("Processing {}", x); // This won't print until consumed
            x * x
        })
        .filter(|&x| x % 2 == 0)
        .take(5);
    
    println!("Iterator created, but nothing computed yet");
    
    // Only now does the computation happen
    let results: Vec<i32> = lazy_computation.collect();
    println!("Results: {:?}", results);
    
    // Infinite iterators
    println!("\nInfinite iterator example:");
    let infinite_squares = (0..)
        .map(|x| x * x)
        .filter(|&x| x % 3 == 0);
    
    let first_10_squares_divisible_by_3: Vec<i32> = infinite_squares
        .take(10)
        .collect();
    
    println!("First 10 squares divisible by 3: {:?}", first_10_squares_divisible_by_3);
}

Parallel Iteration with Rayon

// Note: This requires the `rayon` crate
// In Cargo.toml: rayon = "1.5"

/*
use rayon::prelude::*;

fn main() {
    let numbers: Vec<i32> = (0..1_000_000).collect();
    
    // Sequential iteration
    let sequential_sum: i32 = numbers.iter().map(|&x| x * x).sum();
    
    // Parallel iteration
    let parallel_sum: i32 = numbers.par_iter().map(|&x| x * x).sum();
    
    println!("Sequential sum: {}", sequential_sum);
    println!("Parallel sum: {}", parallel_sum);
    
    // Parallel filtering and mapping
    let processed: Vec<i32> = numbers
        .par_iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * 3)
        .collect();
    
    println!("Processed {} even numbers", processed.len());
}
*/

Performance Considerations

Iterator vs For Loop

fn main() {
    let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Iterator approach (often optimizes better)
    let sum_iterator: i32 = data.iter().map(|&x| x * x).sum();
    
    // Traditional loop
    let mut sum_loop = 0;
    for &item in &data {
        sum_loop += item * item;
    }
    
    println!("Iterator sum: {}", sum_iterator);
    println!("Loop sum: {}", sum_loop);
    
    // Benchmarking shows iterators often perform as well or better
    // due to compiler optimizations
}

Zero-Cost Abstractions

// High-level iterator code
fn high_level_processing(data: &[i32]) -> Vec<i32> {
    data.iter()
        .filter(|&&x| x > 0)
        .map(|&x| x * 2)
        .filter(|&x| x < 100)
        .collect()
}

// Equivalent low-level code (what compiler might generate)
fn low_level_processing(data: &[i32]) -> Vec<i32> {
    let mut result = Vec::new();
    for &item in data {
        if item > 0 {
            let doubled = item * 2;
            if doubled < 100 {
                result.push(doubled);
            }
        }
    }
    result
}

fn main() {
    let data = vec![-5, 10, 30, 45, 60, 75, 90];
    
    let result1 = high_level_processing(&data);
    let result2 = low_level_processing(&data);
    
    println!("High-level result: {:?}", result1);
    println!("Low-level result: {:?}", result2);
    
    assert_eq!(result1, result2);
}

Best Practices

1. Prefer Iterators Over Loops

// Good: Iterator chain
fn process_scores(scores: &[i32]) -> Vec<i32> {
    scores.iter()
        .filter(|&&score| score >= 60)
        .map(|&score| score * 2)
        .collect()
}

// Avoid: Manual loop when iterator would work
fn process_scores_manual(scores: &[i32]) -> Vec<i32> {
    let mut result = Vec::new();
    for &score in scores {
        if score >= 60 {
            result.push(score * 2);
        }
    }
    result
}

2. Use Appropriate Closure Traits

// Use Fn when possible (most flexible)
fn apply_readonly<F>(data: &[i32], f: F) -> Vec<i32>
where
    F: Fn(i32) -> i32,
{
    data.iter().map(|&x| f(x)).collect()
}

// Use FnMut when you need to mutate captured variables
fn apply_with_state<F>(data: &[i32], mut f: F) -> Vec<i32>
where
    F: FnMut(i32) -> i32,
{
    data.iter().map(|&x| f(x)).collect()
}

// Use FnOnce when closure consumes captured variables
fn apply_once<F>(data: &[i32], f: F) -> i32
where
    F: FnOnce(&[i32]) -> i32,
{
    f(data)
}

3. Leverage Lazy Evaluation

// Good: Lazy evaluation, only processes what's needed
fn find_first_large_prime(start: u64) -> Option<u64> {
    (start..)
        .filter(|&n| is_prime(n))
        .find(|&n| n > 1000)
}

// Avoid: Eager evaluation
fn find_first_large_prime_eager(start: u64) -> Option<u64> {
    let primes: Vec<u64> = (start..10000) // Arbitrary upper bound
        .filter(|&n| is_prime(n))
        .collect();
    
    primes.into_iter().find(|&n| n > 1000)
}

fn is_prime(n: u64) -> bool {
    if n < 2 { return false; }
    if n == 2 { return true; }
    if n % 2 == 0 { return false; }
    
    let sqrt_n = (n as f64).sqrt() as u64;
    (3..=sqrt_n).step_by(2).all(|i| n % i != 0)
}

4. Chain Operations Efficiently

use std::collections::HashMap;

// Good: Single iterator chain
fn analyze_text(text: &str) -> HashMap<String, usize> {
    text.split_whitespace()
        .map(|word| word.to_lowercase())
        .filter(|word| word.len() > 3)
        .fold(HashMap::new(), |mut acc, word| {
            *acc.entry(word).or_insert(0) += 1;
            acc
        })
}

// Avoid: Multiple passes
fn analyze_text_multiple_passes(text: &str) -> HashMap<String, usize> {
    let words: Vec<String> = text.split_whitespace()
        .map(|word| word.to_lowercase())
        .collect();
    
    let filtered: Vec<String> = words.into_iter()
        .filter(|word| word.len() > 3)
        .collect();
    
    let mut counts = HashMap::new();
    for word in filtered {
        *counts.entry(word).or_insert(0) += 1;
    }
    
    counts
}

5. Use collect() Judiciously

// Good: Only collect when you need the whole collection
fn process_and_collect(data: &[i32]) -> Vec<i32> {
    data.iter()
        .map(|&x| x * 2)
        .filter(|&x| x > 10)
        .collect()
}

// Good: Don't collect if you're just going to iterate again
fn process_without_collect(data: &[i32]) -> i32 {
    data.iter()
        .map(|&x| x * 2)
        .filter(|&x| x > 10)
        .sum() // Don't collect, just sum directly
}

// Avoid: Unnecessary intermediate collections
fn avoid_intermediate_collect(data: &[i32]) -> i32 {
    let doubled: Vec<i32> = data.iter().map(|&x| x * 2).collect();
    let filtered: Vec<i32> = doubled.iter().filter(|&&x| x > 10).cloned().collect();
    filtered.iter().sum()
}

Closures and iterators are powerful tools that enable expressive, efficient functional programming in Rust. They leverage zero-cost abstractions to provide high-level expressiveness without sacrificing performance. Master these concepts to write more elegant and maintainable Rust code.