Closures: Anonymous Functions
fn main() {
// Closure syntax: |params| body
let add = |a, b| a + b;
let double = |x: i32| x * 2;
let greet = |name: &str| format!("Hello, {}!", name);
println!("{}", add(2, 3));
println!("{}", greet("Rust"));
// Closures CAPTURE their environment
let factor = 3;
let multiply = |x| x * factor; // Captures factor by copy
println!("{}", multiply(5)); // 15
let text = String::from("hello");
let contains_l = |c| text.contains(c); // Captures text by reference
println!("{}", contains_l('l'));
println!("{}", text); // text still valid
// move closure: take ownership of captured values
let text2 = String::from("world");
let owns_text = move || format!("I own: {}", text2); // text2 moved in
println!("{}", owns_text());
// println!("{}", text2); // ERROR: moved into closure
// Closures as arguments (Fn, FnMut, FnOnce)
fn apply i32>(f: F, x: i32) -> i32 { f(x) }
fn apply_twice i32>(f: F, x: i32) -> i32 { f(f(x)) }
println!("{}", apply(|x| x + 1, 5)); // 6
println!("{}", apply_twice(|x| x * 2, 3)); // 12
} Fn Traits
// Fn: can be called multiple times, immutable capture
// FnMut: can be called multiple times, mutable capture
// FnOnce: can only be called once (consumes captured vars)
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n // Returns a closure!
}
fn make_counter() -> impl FnMut() -> i32 {
let mut count = 0;
move || { count += 1; count } // FnMut: modifies captured state
}
fn main() {
let add5 = make_adder(5);
println!("{} {}", add5(10), add5(20)); // 15 25
let mut counter = make_counter();
println!("{} {} {}", counter(), counter(), counter()); // 1 2 3
// Sort with closure
let mut names = vec!["Charlie", "Alice", "Bob"];
names.sort_by(|a, b| a.len().cmp(&b.len()));
println!("{:?}", names); // ["Bob", "Alice", "Charlie"]
}🎯 Practice
- Write a function make_multiplier(n: i32) -> impl Fn(i32) -> i32 returning a closure
- Write fn compose<A,B,C>(f: impl Fn(A)->B, g: impl Fn(B)->C) -> impl Fn(A)->C
- Use sort_by_key with a closure to sort Vec<(String, i32)> by the i32 field descending
🎉 Key Takeaways
- Closures capture environment: by reference (default), by copy (for Copy types), or by move
- use move to take ownership of captured values (needed for threads, returning closures)
- Fn: immutable capture. FnMut: mutable. FnOnce: consumed on call
- impl Fn(T)->U in return position for returning closures