Closures in Rust: magic or strict mathematics?
\n\nIf you've ever written something like |x| x + 1 in Rust, you've used closures. But as soon as you try to pass this closure to a function or store it in a struct, the compiler suddenly starts demanding: "Which trait? Fn, FnMut, or FnOnce?".
And many people get stuck here. Especially if you come from JavaScript, Python, or Ruby, where closures are "just functions that capture variables." In Rust, it's more complicated (and more interesting).
\n\nToday we'll figure out what actually happens under the hood when Rust decides which trait to give your closure. This isn't magic — it's a strict type system that protects you from data races and dangling pointers. Let's go!
\n\nThe gist: three closure traits
\n\nIn Rust, there are three traits that closures implement:
\n\n- \n
FnOnce— can be called once. Takes ownership of captured variables. \n FnMut— can be called multiple times, but can mutate captured variables (takes&mut). \n Fn— can be called multiple times and does not mutate captured variables (takes&). \n
Each closure automatically implements all applicable traits. That is, if a closure implements Fn, it automatically implements both FnMut and FnOnce. But the reverse does not work.
Why? Because Fn is the most restrictive, and FnOnce is the least restrictive. The inheritance hierarchy looks like this:
FnOnce ← FnMut ← Fn\n\nThat is, FnMut: FnOnce, and Fn: FnMut. Remember this ladder — it's key.
How does Rust decide which trait to give a closure?
\n\nThe compiler looks at how exactly the closure uses captured variables. There are three capture modes:
\n\n- \n
- By reference (
&T) — the closure only reads the variable. \n - By mutable reference (
&mut T) — the closure mutates the variable. \n - By value (
T) — the closure takes ownership of the variable. \n
Here's how this affects the traits:
\n\n| Capture mode | Implemented traits | Example |
|---|---|---|
Only &T | Fn + FnMut + FnOnce | || println!("{}", x) |
&mut T (but not T) | FnMut + FnOnce | || x += 1 |
T (ownership) | Only FnOnce | || drop(x) or move || x |
Let's break down each case with live code.
\n\nExample 1: Read-only → Fn
\n\nlet x = 42;\nlet read_closure = || println!("x = {}", x);\n\n// Can be called any number of times\nread_closure();\nread_closure();\n\n// And even passed to a function requiring Fn\nfn call_fn<F: Fn()>(f: F) {\n f();\n}\ncall_fn(read_closure);\n\nHere the closure simply reads x. It doesn't need to mutate it or take ownership. Rust understands: "OK, this is safe — we give Fn".
Example 2: Mutation → FnMut
\n\nlet mut x = 42;\nlet mut mut_closure = || {\n x += 1;\n println!("x = {}", x);\n};\n\nmut_closure(); // x = 43\nmut_closure(); // x = 44\n\n// Pass to a function requiring FnMut\nfn call_fn_mut<F: FnMut()>(mut f: F) {\n f();\n f();\n}\ncall_fn_mut(mut_closure);\n\nNotice: we declared mut_closure as let mut. Why? Because calling FnMut requires a muta