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