GGistDev

Closures in Rust

Closures are anonymous functions that can capture their environment. They implement one of Fn, FnMut, or FnOnce depending on how they capture.

Syntax and inference

let add = |a: i32, b: i32| a + b;      // types inferred
after_map(vec![1,2,3], |x| x * 2);

Capture and traits

  • Fn: captures by shared reference
  • FnMut: captures by mutable reference
  • FnOnce: captures by value (moves) and may be called once
fn call_twice<F: Fn(i32)>(f: F) { f(1); f(2); }
fn with_acc<F: FnMut(i32)>(mut f: F) { f(1); f(2); }
fn consume<F: FnOnce(String)>(f: F) { f("hi".into()); }

The compiler picks the least restrictive trait automatically.

move closures

Force capture by value; required for threads.

let v = vec![1,2,3];
std::thread::spawn(move || {
    println!("{:?}", v);
}).join().unwrap();

Returning and storing closures

Use impl Trait or trait objects.

fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y }

struct Handler<F: Fn(&str)> { cb: F }
let h = Handler { cb: |s| println!("{s}") };

let boxed: Box<dyn Fn(i32) -> i32> = Box::new(|n| n + 1);

Closures capturing references must outlive their uses; use move or adjust lifetimes when needed.

Using closures with iterators

let evens: Vec<_> = (0..10).filter(|n| n % 2 == 0).collect();

Tips

  • Choose the simplest bound (Fn/FnMut/FnOnce) that works for your API.
  • Prefer move for threads and to avoid borrowing pitfalls.