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 &i32, dereferences 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 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 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.