When I first saw this in Rust:
fn apply<F: Fn()>(f: F) {
f();
}
I thought — okay, Fn, fine. Then I saw FnMut. Then FnOnce. And I had no idea why there were three of them, when to use which, or what the difference actually was.
Every explanation I found went straight into "closures capture their environment by reference, by mutable reference, or by value."
That sentence is technically correct. It also told me nothing useful.
So let's do this differently. No jargon first. Just a real-life situation.
📖 A Real Life Example First
Imagine you hire three different people to do a job for you.
Person 1 — The Reader
You hand them your notebook. They read it as many times as they want. They never change anything. You can call ten of these people simultaneously — they all just read the same notebook.
Person 2 — The Editor
You hand them your notebook. They read it and make changes. Only one person can edit at a time. But you can ask them to edit multiple times.
Person 3 — The One-Time Guy
You hand them the notebook and they take it with them. They do their job — maybe they tear out all the pages and mail them somewhere. Once they're done, the notebook is gone. You can't call them again.
That's the whole idea behind Fn, FnMut, and FnOnce.
📖
Fn= The Reader — borrows, never changes, can be called many times✏️
FnMut= The Editor — borrows and changes, can be called many times📦
FnOnce= The One-Time Guy — takes ownership, can only be called once
Keep this in your head. Let's bring it into Rust now.
🤔 What Even Is a Closure?
Before the three traits, let's make sure closures themselves are clear.
A closure is just a function you define inline, that can reach out and use variables from the surrounding code.
let name = String::from("Ravi");
let greet = || {
println!("Hello, {}!", name); // using `name` from outside
};
greet(); // Hello, Ravi!
That || { ... } is a closure. The || is where parameters go (empty here). And notice — it's using name which lives outside the closure. That's called capturing the environment.
Now, how the closure captures that variable — read-only, read-write, or taking full ownership — is exactly what determines which of the three traits it gets.
📖 Fn — The Reader (Immutable Borrow)
let message = String::from("Server started");
let log = || {
println!("{}", message); // just reading message
};
log(); // Server started
log(); // Server started again — works fine
log(); // Works a third time too
This closure only reads message. It never changes it, never takes it away.
Rust sees this and thinks: "This closure is safe to call as many times as we want."
So Rust gives it the Fn trait.
The Reader analogy: The notebook stays on your desk. Anyone can read it. Nothing ever changes. You can call this closure a hundred times.
Where you'll see this in real code
fn run_twice<F: Fn()>(f: F) {
f();
f();
}
let city = String::from("Ahmedabad");
run_twice(|| println!("City: {}", city));
run_twice calls f twice — only possible because f is Fn. If it were FnOnce, the second call would fail because the closure would be used up after the first.
✏️ FnMut — The Editor (Mutable Borrow)
let mut count = 0;
let mut increment = || {
count += 1; // changing count — mutable borrow
println!("Count: {}", count);
};
increment(); // Count: 1
increment(); // Count: 2
increment(); // Count: 3
This closure is modifying count. It needs to change something outside itself.
Rust gives it FnMut — mutable, but still borrowing. Not owning.
The Editor analogy: The notebook stays with you, but the editor is actively scribbling in it. You can ask them to edit multiple times. But only one editor at a time.
Where you'll see this in real code
fn apply_three_times<F: FnMut()>(mut f: F) {
f();
f();
f();
}
let mut total = 0;
apply_three_times(|| {
total += 10;
});
println!("Total: {}", total); // Total: 30
Notice mut f in the function signature — when you accept FnMut, you need to mark the parameter as mut because calling it changes internal state.
📦 FnOnce — The One-Time Guy (Takes Ownership)
let name = String::from("Divyesh");
let introduce = move || {
println!("Hi, I am {}!", name);
// name is now owned by this closure
};
introduce(); // Hi, I am Divyesh!
// introduce(); // ERROR — can't call again, name is gone
This closure used move — it took full ownership of name. Once it runs, name is consumed. Nothing left for a second call.
Rust gives it FnOnce — once, and only once.
The One-Time Guy analogy: You handed the notebook over. They took it. It left your hands. There's no calling them back.
Where you'll see this in real code — thread::spawn
This is the most important real-world use of FnOnce:
use std::thread;
let message = String::from("Hello from thread");
thread::spawn(move || {
println!("{}", message); // message moved into the thread
});
thread::spawn requires FnOnce + Send + 'static. Why FnOnce? Because a spawned thread runs once. Ownership moves in, the thread runs, done.
🔑 The Hierarchy — The Key Insight Nobody Explains
Every
Fnis alsoFnMut. EveryFnMutis alsoFnOnce.
Think about it:
- If a closure only reads (
Fn), it can obviously also be called just once (FnOnce) - If a closure can be called repeatedly with mutation (
FnMut), it can obviously be called just once (FnOnce)
So the traits form a hierarchy:
FnOnce ← least restrictive — every closure implements this
└── FnMut ← closure can be called multiple times
└── Fn ← most restrictive — safe to call anywhere, anytime
In practice: use the least restrictive bound that still does what you need.
// Need to call it once?
fn do_once<F: FnOnce()>(f: F) { f(); }
// Need to call it multiple times with state change?
fn do_many<F: FnMut()>(mut f: F) { f(); f(); f(); }
// Need to call it anywhere, read-only?
fn do_shared<F: Fn()>(f: F) { f(); f(); }
🔔 Real Example — All Three Together
fn send_once<F: FnOnce()>(sender: F) {
sender();
}
fn send_repeatedly<F: FnMut()>(mut sender: F, times: u32) {
for _ in 0..times {
sender();
}
}
fn preview<F: Fn()>(sender: F) {
println!("--- Preview ---");
sender();
sender();
println!("--- End ---");
}
fn main() {
// FnOnce — consumes the recipient string
let recipient = String::from("ravi@example.com");
send_once(move || {
println!("Sending email to {}", recipient);
});
// FnMut — counts SMS sent
let mut sms_count = 0;
send_repeatedly(|| {
sms_count += 1;
println!("SMS #{} sent", sms_count);
}, 3);
// Fn — read-only preview
let channel = String::from("Push Notification");
preview(|| {
println!("Channel: {}", channel);
});
}
Output:
Sending email to ravi@example.com
SMS #1 sent
SMS #2 sent
SMS #3 sent
--- Preview ---
Channel: Push Notification
Channel: Push Notification
--- End ---
⚡ The move Keyword — Quick Clarification
move forces the closure to take ownership of captured variables instead of borrowing them.
let data = String::from("important");
// Without move — borrows data
let borrow_closure = || println!("{}", data);
// With move — owns data
let own_closure = move || println!("{}", data);
move doesn't automatically make something FnOnce. It just means the closure owns its captured variables. Whether it's Fn, FnMut, or FnOnce still depends on what the closure does with those variables inside.
⚖️ Quick Reference Table
| Trait | Captures how | Call how many times | Real life |
|---|---|---|---|
Fn |
Immutable borrow &T
|
Unlimited | The Reader |
FnMut |
Mutable borrow &mut T
|
Unlimited | The Editor |
FnOnce |
By value (ownership) | Once only | The One-Time Guy |
🤔 When to Use Which — The Simple Rule
Use FnOnce when:
- Spawning threads (
thread::spawn) - The closure needs to consume or drop something
- Something that should only happen once
Use FnMut when:
- Building counters or accumulators
- Iterators with state
- Anything updating a variable across multiple calls
Use Fn when:
- Callbacks called from multiple places
- Anything you want to store and reuse safely
- Default choice when mutation isn't needed
🦀 Why This Matters
Rust's whole promise: flexibility shouldn't cost you performance.
Instead of one vague "callable" type, Rust gives you three — each with a precise contract about what the closure does with its environment. The compiler enforces that contract at compile time. Zero runtime cost.
📖
Fn= just reads. Call it forever.✏️
FnMut= reads and writes. Call it many times.📦
FnOnce= takes ownership. Call it once, then it's done.
The notebook never lied to you. 📓
Follow me for more Rust deep dives 🦀
Missed the previous post? Check out Static Dispatch, Dynamic Dispatch & Monomorphization in Rust — closures and dispatch use the same underlying trait system, so that one pairs well with this.
Drop a ❤️ if this helped, and leave a comment below with which part finally made it click for you. 👇

Top comments (0)