Question
In C or C++, callbacks are often implemented with a plain function pointer, sometimes alongside a void* user data pointer. For example:
typedef void (*Callback)();
class Processor
{
public:
void setCallback(Callback c)
{
mCallback = c;
}
void processEvents()
{
// ...
mCallback();
}
private:
Callback mCallback;
};
What is the idiomatic way to implement this pattern in Rust?
More specifically:
- What type should a
set_callback()method accept? - What type should the stored callback field be?
- Should the callback use
Fn,FnMut, orFnOnce? - Should it be stored in a
Box?
A practical example of the usual Rust approach would be helpful.
Short Answer
By the end of this page, you will understand how Rust models callbacks using closures and function traits, when to use Fn, FnMut, or FnOnce, and why callbacks are often stored as Box<dyn FnMut()> or a generic type. You will also see how this differs from C-style function pointers and how Rust closures safely replace the old function pointer + userdata pattern.
Concept
Rust usually does not model callbacks the same way C and C++ do.
In C, a callback is often:
- a function pointer
- plus a
void*pointer for extra context
That extra context is needed because plain C function pointers cannot capture variables.
Rust closures solve this directly. A closure can capture values from its surrounding scope, so the language already gives you a safer, more expressive replacement for function pointer + userdata.
The core idea
Rust has three main closure traits:
Fn— the closure only reads captured valuesFnMut— the closure may modify captured valuesFnOnce— the closure may consume captured values
These traits describe how the callback uses its captured environment.
Why this matters
Choosing the right callback type affects:
- whether the callback can be called multiple times
- whether it can mutate state
- whether it can be stored in a struct
- whether you use static dispatch (generics) or dynamic dispatch (
Box<dyn ...>)
Function pointers vs closures
A plain function pointer in Rust looks like this:
fn my_handler() {
println!("called");
}
let f: fn() = my_handler;
This is closest to a C callback. But it only works for plain functions, not closures that capture values.
If you want the callback to carry state, the idiomatic Rust approach is usually a closure:
let name = String::from("job-1");
let callback = move || println!("processing {name}");
That closure captures name, which a plain fn() cannot do.
The usual choice for stored callbacks
If your struct needs to store a callback and call it multiple times, a common choice is:
Box<dyn FnMut()>
Why?
dyn FnMut()allows storing any closure or function with that callable shapeBoxgives it a known size, which is needed because trait objects are dynamically sizedFnMutis flexible: it supports mutable closures and also works with non-mutating ones in many cases
If the callback never mutates captured state, Fn is more restrictive and can be a good choice.
If the callback is called only once, FnOnce may fit better, but storing and repeatedly invoking FnOnce is not appropriate.
Two common callback styles in Rust
1. Generic callback field
Use this when the callback type is known at compile time and the struct only needs one concrete callback type.
struct Processor<F>
where
F: FnMut(),
{
callback: F,
}
This avoids heap allocation and dynamic dispatch.
2. Boxed trait object callback
Use this when you want more flexibility, such as setting callbacks at runtime.
struct Processor {
callback: Box<dyn FnMut()>,
}
This is often the most practical equivalent to OOP-style callback storage.
Mental Model
Think of a Rust callback as a small object with an action inside it.
In C, you often need two separate pieces:
- the function to run
- the data it needs
That is like storing:
- a recipe card
- and a bag of ingredients next to it
In Rust, a closure bundles both together. It is like a recipe card that already has its own ingredients attached.
The three closure traits describe how the callback uses those ingredients:
Fn: only looks at themFnMut: may rearrange or update themFnOnce: may use them up completely
So when choosing a callback type, ask:
- Will I call it once or many times?
- Does it need to change captured state?
- Do I want maximum flexibility or maximum performance?
That usually leads to the right trait.
Syntax and Examples
Basic stored callback with Box<dyn FnMut()>
This is a very common and idiomatic pattern when a struct stores a callback that may be changed at runtime.
struct Processor {
callback: Box<dyn FnMut()>,
}
impl Processor {
fn new() -> Self {
Self {
callback: Box::new(|| {}),
}
}
fn set_callback<F>(&mut self, callback: F)
where
F: FnMut() + 'static,
{
self.callback = Box::new(callback);
}
fn process_events(&mut self) {
// ... do work ...
(self.callback)();
}
}
fn main() {
let mut processor = Processor::new();
let mut count = 0;
processor.set_callback(move || {
count += 1;
println!("callback called {count} time(s)");
});
processor.process_events();
processor.process_events();
}
Why FnMut here?
The closure updates count, so it needs mutable access to captured state. That means FnMut is the correct trait.
Why Box?
Closures have unique compiler-generated types. If you want to store different closures in the same field, you usually use a trait object like dyn FnMut(). Since trait objects are dynamically sized, they are commonly stored behind a pointer such as Box.
Using Fn instead
If the callback does not mutate captured data, you can use Fn:
struct Processor {
callback: Box<dyn Fn()>,
}
impl Processor {
fn set_callback<F>(&mut self, callback: F)
where
F: Fn() + 'static,
{
self.callback = Box::new(callback);
}
}
Using a plain function pointer
If you only need non-capturing functions, you can use fn() directly:
struct Processor {
callback: fn(),
}
impl Processor {
fn set_callback(&mut self, callback: fn()) {
self.callback = callback;
}
fn process_events(&self) {
(self.callback)();
}
}
fn hello() {
println!("hello");
}
This is simple, but much less flexible than closures.
Step by Step Execution
Consider this example:
struct Processor {
callback: Box<dyn FnMut()>,
}
impl Processor {
fn new() -> Self {
Self {
callback: Box::new(|| {}),
}
}
fn set_callback<F>(&mut self, callback: F)
where
F: FnMut() + 'static,
{
self.callback = Box::new(callback);
}
fn process_events(&mut self) {
(self.callback)();
}
}
fn main() {
let mut processor = Processor::new();
let message = String::from("event handled");
processor.set_callback(move || {
println!("{message}");
});
processor.process_events();
}
What happens step by step?
1. Processor::new() creates a default callback
callback: Box::new(|| {})
This stores an empty closure so the field always has a valid callable value.
2. message is created in main
let message = String::from("event handled");
This string lives in main.
3. A closure is passed to set_callback()
move || {
println!("{message}");
}
The move keyword moves message into the closure, so the callback owns it.
4. set_callback() boxes the closure
self.callback = Box::new(callback);
Now the Processor stores the closure behind a trait object.
5. process_events() calls the stored callback
(self.callback)();
This executes the closure, which prints the captured message.
Why is 'static used?
The signature uses:
F: FnMut() + 'static
This means the stored callback must not borrow short-lived local values. Since the closure is stored inside the struct, Rust must ensure it stays valid as long as needed.
Using move often helps satisfy this requirement by moving owned data into the closure.
Real World Use Cases
Callbacks in Rust are commonly used anywhere code needs to run user-provided behavior later.
GUI and event systems
A button click handler is a callback:
button.on_click(|| {
println!("button clicked");
});
Background processing
A worker may notify the caller when work is done:
- processing events
- finishing a download
- handling a file change
Iteration and collection APIs
Methods like map, filter, and for_each all accept closures as callbacks.
let numbers = vec![1, 2, 3];
let doubled: Vec<_> = numbers.into_iter().map(|n| n * 2).collect();
Server and API code
Web frameworks often use closures for:
- route handlers
- middleware
- request processing
Testing and mocking
A callback can inject custom behavior during tests, such as recording whether an action was triggered.
Embedded and systems programming
Rust still supports low-level callback patterns, including plain function pointers when needed, especially for C interop. But in native Rust APIs, closures are usually more ergonomic and safer.
Real Codebase Usage
In real projects, developers often choose callback styles based on ownership, lifetime, and how often the callback runs.
Common pattern: store a boxed callback
callback: Box<dyn FnMut(Event)>
This is common when:
- the callback is configurable at runtime
- different callers may provide different closures
- you want one stable field type in a struct
Common pattern: generic parameter for performance
struct Processor<F>
where
F: FnMut(),
{
callback: F,
}
This is common when:
- the callback is fixed when the struct is created
- performance matters
- you want static dispatch and no heap allocation
Validation and guard clauses
Real code often checks whether a callback exists before calling it. One simple approach is Option:
struct Processor {
callback: Option<Box<dyn FnMut()>>,
}
impl Processor {
fn process_events(&mut self) {
if let Some(callback) = self.callback.as_mut() {
callback();
}
}
}
This avoids needing a default no-op callback.
Error handling callbacks
Callbacks often accept data:
Box<dyn FnMut(Result<String, String>)>
That lets the processing code report success or failure.
Shared mutable state
In larger systems, callbacks sometimes need shared ownership. Then developers use Rc<RefCell<T>> or Arc<Mutex<T>> depending on single-threaded or multi-threaded needs.
Choosing Fn vs FnMut
In practice, many APIs choose FnMut because it is flexible. A callback that only reads state can still often be used where FnMut is accepted, but a callback that mutates captured values cannot be used where only Fn is allowed.
Common Mistakes
1. Using fn() when a closure needs captured state
Broken example:
struct Processor {
callback: fn(),
}
fn main() {
let message = String::from("hello");
let mut p = Processor {
callback: || println!("{message}"),
};
}
This fails because fn() is only for plain function pointers, not capturing closures.
Fix
Use a closure trait object or a generic parameter:
callback: Box<dyn Fn()>
2. Choosing Fn when the callback mutates state
Broken example:
let mut count = 0;
let callback = move || {
count += 1;
};
This closure needs FnMut, not Fn.
Fix
Use FnMut in the API:
F: FnMut()
3. Forgetting that calling FnMut needs mutable access
Broken example:
struct Processor {
callback: Box<dyn FnMut()>,
}
impl Processor {
fn process_events(&self) {
(self.callback)();
}
}
This fails because calling an FnMut callback requires mutable access.
Fix
Make the method take &mut self:
fn process_events(&mut self) {
(self.callback)();
}
4. Trying to store a borrowed closure without handling lifetimes
Broken example:
fn set_callback<F>(&mut self, callback: F)
where
F: FnMut(),
{
self.callback = Box::new(callback);
}
This may fail depending on how the closure borrows local values.
Fix
Use 'static when storing long-term:
F: FnMut() + 'static
And prefer move closures when appropriate.
5. Using FnOnce for a repeatedly called callback
FnOnce means the closure may consume captured values and can only be called once.
Rule of thumb
- use
FnOncefor one-time execution - use
FnMutfor repeated mutable callbacks - use
Fnfor repeated read-only callbacks
Comparisons
Callback type comparison
| Type | Can capture environment? | Can mutate captured state? | Can be called multiple times? | Common use |
|---|---|---|---|---|
fn() | No | No | Yes | Plain function pointer, C-like callback |
Fn | Yes | No | Yes | Read-only closure callback |
FnMut | Yes | Yes | Yes | Stateful callback used repeatedly |
FnOnce | Yes | Yes | Not necessarily | One-time callback that may consume values |
Box<dyn FnMut()> vs generic F: FnMut()
| Approach | Pros | Cons | Good when |
|---|---|---|---|
Box<dyn FnMut()> | Flexible, easy to store, runtime replaceable | Heap allocation, dynamic dispatch | Callback changes at runtime |
F: FnMut() | No boxing, static dispatch, often faster | Struct becomes generic, less flexible for swapping types | Callback known at construction time |
Fn vs FnMut
| Trait | Best for |
|---|---|
Fn | Callbacks that only read captured values |
FnMut | Callbacks that update counters, buffers, logs, or internal state |
Which one should you choose?
A practical rule:
- Use
fn()only if you truly want plain function pointers. - Use
Fnif the callback is read-only. - Use
FnMutif the callback might keep state between calls. - Use
FnOnceif the callback is consumed after one execution. - Use
Box<dyn FnMut()>when storing a runtime-configurable callback in a struct.
Cheat Sheet
Quick reference
Plain function pointer
fn handler() {}
let f: fn() = handler;
- Cannot capture variables
- Closest to C callback style
Stored closure callback
struct Processor {
callback: Box<dyn FnMut()>,
}
Setter method
fn set_callback<F>(&mut self, callback: F)
where
F: FnMut() + 'static,
{
self.callback = Box::new(callback);
}
Call the callback
(self.callback)();
If the callback is FnMut, the method usually needs &mut self.
Trait meanings
Fn= read captured stateFnMut= modify captured stateFnOnce= consume captured state
Common choices
- Repeated stored callback:
Box<dyn FnMut()> - Read-only stored callback:
Box<dyn Fn()> - One-time callback:
Box<dyn FnOnce()>is less common to store and more common to consume immediately - Compile-time callback type:
F: FnMut()
Lifetimes
If storing the callback in a struct:
F: 'static
This is often needed unless the struct itself has explicit lifetime parameters.
Optional callback
callback: Option<Box<dyn FnMut()>>
Useful when no callback is required initially.
FAQ
Should I use Fn, FnMut, or FnOnce for a callback in Rust?
Use Fn for read-only callbacks, FnMut for callbacks that update internal state, and FnOnce for callbacks that may consume captured values and run only once.
What is the idiomatic Rust replacement for a C callback plus void* userdata?
A closure is the usual replacement. It captures the needed data directly, so you usually do not need a separate user data pointer.
Why do I need Box<dyn FnMut()> to store a callback in a struct?
Different closures have different concrete types. A trait object like dyn FnMut() lets you store any compatible closure, and Box gives it a fixed-sized pointer for storage.
Can I use a plain function pointer in Rust?
Yes. Use fn() if you only need plain functions and do not need captured state.
Why does calling an FnMut callback require &mut self?
Because FnMut may change its internal captured state each time it runs, so Rust requires mutable access to call it.
Why is 'static often required for stored callbacks?
Because the callback may outlive the local scope where it was created. 'static ensures it does not borrow short-lived data.
Is FnMut a safe default?
Often yes, especially for stored callbacks. It is flexible enough for many cases while still allowing repeated calls.
When should I prefer a generic callback type over Box<dyn FnMut()>?
Prefer generics when the callback type is fixed at compile time and you want static dispatch and no heap allocation.
Mini Project
Description
Build a small event processor that lets a user register a callback and then triggers that callback whenever an event is processed. This demonstrates the most practical Rust callback pattern: storing a closure in a struct and invoking it later.
Goal
Create a Processor type that stores a callback, allows replacing it, and calls it every time an event is processed.
Requirements
- Create a
Processorstruct that stores a callback. - Add a
set_callbackmethod that accepts a closure. - Add a
process_eventmethod that invokes the stored callback. - Make the callback able to mutate captured state.
- Show the callback being called multiple times.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.