Question
Can a function be passed as a parameter in Rust? If so, what is the correct syntax, and if not, what is the best alternative?
For example, I know a function can be assigned to a function pointer variable like this:
fn example() {
let fun: fn(i32) -> i32;
fun = fun_test;
fun(5_i32);
}
fn fun_test(value: i32) -> i32 {
println!("{}", value);
value
}
But I want to pass a function into another function, something conceptually like this:
fn fun_test(value: i32, /* some function type here */) -> i32 {
println!("{}", value);
value
}
What is the correct way to do this in Rust?
Short Answer
By the end of this page, you will understand how Rust lets you pass behavior into functions using function pointers and closures. You will learn the syntax for fn parameter types, when to use generics with Fn, FnMut, or FnOnce, and how this pattern is used in real Rust code.
Concept
In Rust, you can pass a function as a parameter.
There are two closely related ideas:
- Function pointers: a parameter typed as
fn(...) -> ... - Closures: anonymous functions that can capture values from their environment
A named function like this:
fn double(x: i32) -> i32 {
x * 2
}
can be passed to another function if the parameter type matches:
fn apply(value: i32, f: fn(i32) -> i32) -> i32 {
f(value)
}
This works because double has the type fn(i32) -> i32.
Why this matters:
- It lets you reuse logic without hardcoding behavior.
- It is useful for callbacks, transformations, validation, and configuration.
- It is the basis for patterns like
map, filtering, custom sorting, and event handling.
In Rust, if you only need to accept a plain function, use fn(...) -> ....
If you also want to accept closures, use a generic parameter with one of the closure traits:
FnFnMutFnOnce
This is common because closures are often more flexible than plain function pointers.
Mental Model
Think of a function parameter as handing someone a tool.
- A normal
i32parameter is like giving someone a number. - A function parameter is like giving someone a machine they can run later.
For example:
5is datadoubleis an instruction for what to do with data
So instead of saying:
Always multiply by 2
you can say:
Here is a function. Use it on the value.
That makes your code more reusable. One function can work with many behaviors, depending on which function you pass in.
Syntax and Examples
Passing a named function
If you want to accept a regular function, use the fn type:
fn apply(value: i32, operation: fn(i32) -> i32) -> i32 {
operation(value)
}
fn square(x: i32) -> i32 {
x * x
}
fn main() {
let result = apply(4, square);
println!("{}", result); // 16
}
How it works
operation: fn(i32) -> i32means:operationis a function- it takes one
i32 - it returns one
i32
squarematches that exact signature, so it can be passed in.
Passing a closure
If you want to accept closures too, use a generic with Fn:
fn apply<F>(value: i32, operation: F) -> i32
where
F: Fn(i32) -> i32,
{
operation(value)
}
fn main() {
let result = apply(4, |x| x + 10);
println!("{}", result); // 14
}
Why use Fn?
A closure like |x| x + 10 is not written as a plain fn item, but it can still behave like a function. Using Fn makes your function more flexible.
Your original idea, corrected
Here is the direct equivalent of passing a function as a parameter:
fn fun_test(value: i32, some_function: fn(i32) -> i32) -> i32 {
some_function(value)
}
fn print_and_return(value: i32) -> i32 {
println!("{}", value);
value
}
fn main() {
let result = fun_test(5, print_and_return);
println!("Result: {}", result);
}
This is valid Rust and is the basic syntax for passing a function pointer.
Step by Step Execution
Consider this code:
fn apply(value: i32, op: fn(i32) -> i32) -> i32 {
op(value)
}
fn increment(x: i32) -> i32 {
x + 1
}
fn main() {
let result = apply(5, increment);
println!("{}", result);
}
Step by step
main()starts running.apply(5, increment)is called.- The value
5is passed intoapplyasvalue. - The function
incrementis passed intoapplyasop. - Inside
apply, Rust executesop(value). - That becomes
increment(5). incrementreturns6.applyreturns6.mainstores that inresult.println!prints6.
Trace table
| Step | Expression | Result |
|---|---|---|
| 1 | apply(5, increment) | enters apply |
| 2 | op(value) | increment(5) |
| 3 | increment(5) | 6 |
| 4 | apply(...) returns | 6 |
| 5 | println! | prints 6 |
Real World Use Cases
Passing functions or closures is useful whenever code needs custom behavior.
Common use cases
- Transforming data
- Apply a function to each item in a list
- Validation
- Pass different validation rules into a shared function
- Callbacks
- Run user-provided logic after an operation completes
- Sorting and filtering
- Decide how items are compared or selected
- Testing
- Pass mock behavior into functions instead of using real dependencies
Example: reusable validator
fn check_value<F>(value: i32, rule: F) -> bool
where
F: Fn(i32) -> bool,
{
rule(value)
}
fn main() {
let is_positive = check_value(10, |x| x > 0);
let is_even = check_value(10, |x| x % 2 == 0);
println!("positive: {}", is_positive);
println!("even: {}", is_even);
}
One function can now support many different rules.
Real Codebase Usage
In real Rust codebases, developers usually choose between function pointers and closure traits based on flexibility.
Common patterns
1. Simple callback with a function pointer
Used when only a plain function is needed.
fn run_callback(callback: fn()) {
callback();
}
This is simple and clear, but less flexible.
2. Generic closure parameter
Used when the caller may pass either a named function or a closure.
fn process<F>(value: i32, f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(value)
}
This is very common in application code and libraries.
3. Guard clauses before calling behavior
fn apply_if_positive<F>(value: i32, f: F) -> Option<i32>
where
F: Fn(i32) -> i32,
{
if value < 0 {
return None;
}
Some(f(value))
}
This combines validation with configurable behavior.
4. Iterator patterns
Rust code often passes closures into iterator methods instead of writing loops manually:
fn main() {
let nums = vec![1, 2, 3, 4];
let doubled: Vec<i32> = nums.into_iter().map(|x| x * 2).collect();
println!("{:?}", doubled);
}
This is one of the most common forms of passing behavior in Rust.
5. Error handling and customization
Closures are often passed to methods like map, and_then, and unwrap_or_else to handle success or failure cases cleanly.
Common Mistakes
1. Using the wrong type syntax
Broken:
fn apply(value: i32, operation(i32) -> i32) -> i32 {
operation(value)
}
Correct:
fn apply(value: i32, operation: fn(i32) -> i32) -> i32 {
operation(value)
}
You must include the parameter name, followed by :, then the type.
2. Confusing fn with Fn
fn(i32) -> i32is a function pointer typeFn(i32) -> i32is a trait bound for closures and callable values
Broken:
fn apply(value: i32, operation: Fn(i32) -> i32) -> i32 {
operation(value)
}
Correct:
fn apply<F>(value: i32, operation: F) -> i32
where
F: Fn(i32) -> i32,
{
operation(value)
}
3. Expecting fn to cover every closure case
Some closures capture variables from their environment:
fn main() {
let amount = 3;
let add_amount = |x| x + amount;
}
A closure like this may not be appropriate as a plain fn parameter. Use Fn bounds instead.
4. Signature mismatch
Broken:
fn apply(value: i32, operation: fn(i32) -> i32) -> i32 {
operation(value)
}
fn greet(name: &str) {
println!("Hello, {}", name);
}
greet cannot be passed to apply because the parameter and return types do not match.
5. Forgetting that return types matter
This will not match fn(i32) -> i32:
fn print_value(x: i32) {
println!("{}", x);
}
It returns (), not i32.
How to avoid these mistakes
- Match the full signature exactly.
- Use
fn(...) -> ...for plain functions. - Use
Fn,FnMut, orFnOncewhen closures are involved. - Let the compiler guide you; Rust error messages are usually specific here.
Comparisons
| Approach | Syntax | Accepts named functions | Accepts closures | Best for |
|---|---|---|---|---|
| Function pointer | fn(i32) -> i32 | Yes | Only non-capturing closures that can coerce | Simple fixed callbacks |
Generic with Fn | F: Fn(i32) -> i32 | Yes | Yes | Most reusable read-only callable behavior |
Generic with FnMut | F: FnMut(i32) -> i32 | Yes | Yes | Closures that modify captured state |
Generic with FnOnce | F: FnOnce(i32) -> i32 | Yes | Yes | Closures that consume captured values |
fn vs Fn
- Use
fnwhen you specifically want a plain function pointer. - Use
Fnwhen you want maximum flexibility.
Example comparison
Function pointer version
fn apply(value: i32, f: fn(i32) -> i32) -> i32 {
f(value)
}
Closure-friendly version
fn apply<F>(value: i32, f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(value)
}
In real projects, the second version is often preferred unless you explicitly need a function pointer.
Cheat Sheet
Quick syntax
Accept a function pointer
fn apply(value: i32, f: fn(i32) -> i32) -> i32 {
f(value)
}
Pass a named function
fn square(x: i32) -> i32 {
x * x
}
let result = apply(4, square);
Accept functions and closures
fn apply<F>(value: i32, f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(value)
}
Pass a closure
let result = apply(4, |x| x + 1);
Rules to remember
fn(i32) -> i32= function pointer typeFn(i32) -> i32= trait bound, used with generics- The full signature must match:
- parameter types
- return type
- Named functions can be passed directly.
- Closures are usually best handled with
Fn,FnMut, orFnOnce.
Closure trait guide
Fn= can be called without changing captured valuesFnMut= may change captured valuesFnOnce= may consume captured values
Good default
If you are unsure and want flexibility, start with:
fn do_work<F>(value: i32, f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(value)
}
FAQ
Can you pass a function as an argument in Rust?
Yes. Use a parameter type like fn(i32) -> i32 for a plain function pointer, or use a generic bound like F: Fn(i32) -> i32 to also support closures.
What is the difference between fn and Fn in Rust?
fn is a concrete function pointer type. Fn is a trait used for closures and other callable values. Fn is usually more flexible.
Can closures be passed where a fn is expected?
Only some closures, such as non-capturing closures, can coerce to fn. Closures that capture variables usually require Fn, FnMut, or FnOnce.
When should I use FnMut instead of Fn?
Use FnMut when the closure needs to modify captured state, such as incrementing a counter.
Why does my function not match fn(i32) -> i32?
Because the signature must match exactly. The input types and return type must be the same.
Can I store a function in a variable in Rust?
Yes. For example:
let f: fn(i32) -> i32 = square;
Is passing a function common in Rust?
Yes. It is very common, especially with iterators, callbacks, validation logic, and configurable behavior.
Mini Project
Description
Build a small Rust program that applies different operations to a number by passing functions and closures into a reusable helper function. This demonstrates how Rust can treat behavior as a parameter, which is useful for callbacks, transformations, and configurable business logic.
Goal
Create a reusable function that can apply different operations, such as doubling, squaring, and adding a captured value, to an input number.
Requirements
- Create one function that accepts a number and a callable operation.
- Pass at least one named function into it.
- Pass at least one closure into it.
- Print the result of each operation.
- Include one closure that captures a local variable.
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.