Question

How to Pass a Function as a Parameter in Rust

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:

  • Fn
  • FnMut
  • FnOnce

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 i32 parameter is like giving someone a number.
  • A function parameter is like giving someone a machine they can run later.

For example:

  • 5 is data
  • double is 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.

Take Quiz

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) -> i32 means:
    • operation is a function
    • it takes one i32
    • it returns one i32
  • square matches 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

  1. main() starts running.
  2. apply(5, increment) is called.
  3. The value 5 is passed into apply as value.
  4. The function increment is passed into apply as op.
  5. Inside apply, Rust executes op(value).
  6. That becomes increment(5).
  7. increment returns 6.
  8. apply returns 6.
  9. main stores that in result.
  10. println! prints 6.

Trace table

StepExpressionResult
1apply(5, increment)enters apply
2op(value)increment(5)
3increment(5)6
4apply(...) returns6
5println!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.

Take Quiz

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) -> i32 is a function pointer type
  • Fn(i32) -> i32 is 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, or FnOnce when closures are involved.
  • Let the compiler guide you; Rust error messages are usually specific here.

Comparisons

ApproachSyntaxAccepts named functionsAccepts closuresBest for
Function pointerfn(i32) -> i32YesOnly non-capturing closures that can coerceSimple fixed callbacks
Generic with FnF: Fn(i32) -> i32YesYesMost reusable read-only callable behavior
Generic with FnMutF: FnMut(i32) -> i32YesYesClosures that modify captured state
Generic with FnOnceF: FnOnce(i32) -> i32YesYesClosures that consume captured values

fn vs Fn

  • Use fn when you specifically want a plain function pointer.
  • Use Fn when 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.

Take Quiz

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 type
  • Fn(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, or FnOnce.

Closure trait guide

  • Fn = can be called without changing captured values
  • FnMut = may change captured values
  • FnOnce = 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.

Take Quiz

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.
Take Quiz

Keep learning

Related questions

View more