12.3 Common Use Cases for Closures
Closures excel at encapsulating behavior concisely.
12.3.1 Iterators
Used heavily with adapters like map, filter, fold:
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<_> = numbers.iter()
.filter(|&&x| x % 2 == 0) // Closure predicate
.collect();
println!("Evens: {:?}", evens); // Output: Evens: [2, 4, 6]
let squares: Vec<_> = numbers.iter()
.map(|&x| x * x) // Closure transformation: takes a shared ref. &i32, deref. to i32
.collect();
println!("Squares: {:?}", squares); // Output: Squares: [1, 4, 9, 16, 25, 36]
}
12.3.2 Custom Sorting
sort_by and sort_by_key use closures for custom logic:
#[derive(Debug)] struct Person { name: String, age: u32 }
fn main() {
let mut people = vec![
Person { name: "Alice".to_string(), age: 30 },
Person { name: "Bob".to_string(), age: 25 },
Person { name: "Charlie".to_string(), age: 35 },
];
// Sort by age using 'sort_by_key'
people.sort_by_key(|p| p.age); // Closure extracts the key (takes shared borrow of p)
println!("Sorted by age: {:?}", people);
// Sort by name length using 'sort_by'
people.sort_by(|a, b| a.name.len().cmp(&b.name.len()));
// Closure compares elements (takes shared borrows of a and b)
println!("Sorted by name length: {:?}", people);
}
12.3.3 Lazy Initialization
Option::unwrap_or_else, Result::unwrap_or_else compute defaults lazily:
fn main() {
let config_path: Option<String> = None;
let path = config_path.unwrap_or_else(|| {
println!("Computing default path..."); // Runs only if None
String::from("/etc/default.conf")
});
println!("Using path: {}", path);
// Output: Computing default path...
// Output: Using path: /etc/default.conf
}
12.3.4 Concurrency and Asynchronous Operations
Essential for passing code (often with captured state via move) to threads or async tasks.