closures (Fn / FnMut / FnOnce)
Often you want to pass a little bit of behaviour to a function — what to do for each element, how to compare two items, what to run when a button is clicked — and that behaviour needs access to some values from where you wrote it. A plain function cannot see your local variables. A closure can: it is an anonymous function that 'closes over' (captures) variables from the surrounding scope, bundling the code together with the data it needs. You write one inline with bars, like |x| x + offset, where offset is a variable from the enclosing function that the closure captures.
Here is where Rust's ownership shows up again, because a closure that captures variables has to decide HOW it holds them, and that choice is encoded in three traits the closure automatically implements. A closure captures by reference if it only reads a value, by mutable reference if it changes one, or by value (moving ownership in) if you write the move keyword or if the closure must outlive the scope. The three traits classify what the closure then needs to be called: FnOnce means the closure can be called at least once but might consume its captures (it moved something out), so it may run only one time — every closure is at least FnOnce. FnMut means it can be called repeatedly and may mutate its captured state between calls (it holds a captured value by &mut), so calling it needs a &mut to the closure. Fn means it can be called repeatedly and only reads its captures (no mutation), so it can be called through a shared &. The traits nest: every Fn is also FnMut and FnOnce, every FnMut is also FnOnce. A function that takes a closure states which trait it needs as a bound — map wants an FnMut it can call once per element, a thread wants a Send + 'static FnOnce, and so on. The compiler picks the most permissive capture that works and the closure implements whichever traits that allows.
Why this matters: closures are how you write the small behaviours that iterators, threads, callbacks, and async tasks are built from — x.iter().map(|n| n * 2).filter(|n| n > 0) is three closures. The Fn/FnMut/FnOnce distinction is not bureaucracy: it is exactly the ownership rules applied to captured data, so a closure that needs to mutate its environment cannot be shared as if it were read-only, and a closure that moved a value in cannot be called twice. The common gotcha is a closure given to a thread or stored beyond the current scope: it must own its captures (use move) so it does not hold a reference that would dangle once the original scope ends — the same lifetime reasoning as everywhere else in Rust, now applied to the data a closure carries.
let offset = 10; let add = |x: i32| x + offset; // Fn: only READS offset println!("{}", add(5)); // 15 let mut count = 0; let mut tick = || { count += 1; }; // FnMut: MUTATES count tick(); tick(); println!("{count}"); // 2 let name = String::from("ada"); let consume = move || name; // FnOnce: MOVES name out, callable once println!("{}", consume()); // "ada"; calling consume() again won't compile
Which Fn trait a closure implements is decided by how it uses its captures: read -> Fn, mutate -> FnMut, consume -> FnOnce.
Fn/FnMut/FnOnce are just the ownership rules applied to captured data, not bureaucracy. A closure stored beyond its scope or sent to a thread must own its captures (use move) or it would hold a reference that dangles once the original scope ends.