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:
() {
();
}
: () = my_handler;
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();
= ;
processor.( || {
count += ;
();
});
processor.();
processor.();
}
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( || {
();
});
processor.();
}
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
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 {
(& ) {
(callback) = .callback.() {
();
}
}
}
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 = || {
count += ;
};
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 |
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.
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.
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
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Convert an Integer to a String in Rust
Learn the current Rust way to convert integers to strings, why `to_str()` no longer works, and when to use `to_string()` or `format!`.