Functions as Arguments in Rust: 3 Ways to Pass a Function

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Function as an Argument in Rust: From Beginner to Pro

\n\n

Have you ever written in Python or JavaScript and easily passed a function into another function? And then switched to Rust and stumbled on a seemingly simple task? Sounds familiar. The question "How do you pass a Rust function as a parameter?" is one of the most popular on Stack Overflow. And for good reason: Rust's type system and ownership add several layers of complexity, but also open up new possibilities.

\n\n

In this article, we'll break down three main approaches: function pointers (fn), closures, and traits. You'll learn when to use which, and stop being afraid of functional style in Rust.

\n\n

Why Is This Important?

\n

The ability to pass functions as parameters is not just an academic trick. It is the foundation for:

\n
    \n
  • Creating flexible and reusable libraries (e.g., iterators, event handlers);
  • \n
  • Implementing the "Strategy" and "Observer" patterns;
  • \n
  • Writing concise and expressive code without a bunch of if-else statements.
  • \n
\n

In Rust, unlike dynamic languages, the compiler strictly checks types and lifetimes. Therefore, you need to clearly understand what you are passing: just a function address, a closure that captures context, or something more complex.

\n\n

Approach 1: Function Pointer — fn

\n

The simplest and most straightforward way. If your function does not capture variables from the outer environment (it is "pure"), you can use the fn type. Note: fn is a type, while Fn, FnMut, and FnOnce are traits. Don't confuse them!

\n\n
fn add_one(x: i32) -> i32 {\n    x + 1\n}\n\n// Function that takes another function\nfn apply_twice(f: fn(i32) -> i32, value: i32) -> i32 {\n    f(f(value))\n}\n\nfn main() {\n    let result = apply_twice(add_one, 5);\n    println!("Result: {}", result); // 7\n}
\n\n

When to use? When the function is known at compile time, does not capture variables, and you don't need the performance of closures. Function pointers are a zero-cost abstraction; they require no allocations.

\n\n

Limitations of fn

\n

The main drawback: you cannot pass a closure that captures variables. The following code will not compile:

\n\n
fn main() {\n    let y = 10;\n    // Closure captures y\n    let closure = |x: i32| -> i32 { x + y };\n    // Error! expected fn item, found closure\n    // let result = apply_twice(closure, 5);\n}
\n\n

For this, you need the Fn, FnMut, and FnOnce traits.

\n\n

Approach 2: Closures and the Fn, FnMut, FnOnce Traits

\n

Rust is a systems language, and it needs to know what happens with memory. Closures can capture variables in three ways:

\n
    \n
  • By reference (&T) — the Fn trait. Can be called any number of times without modifying the captured data.
  • \n
  • By mutable reference (&mut T) — the FnMut trait. Can be called many times, but the data may be modified.
  • \n
  • By value (T) — the FnOnce trait. Can be called only once, after which the closure is "consumed".
  • \n
\n\n

Example with Fn

\n
fn apply_twice_fn<F>(f: F, value: i32) -> i32\nwhere\n    F: Fn(i32) -> i32,\n{\n    f(f(value))\n}\n\nfn main() {\n    let y = 10;\n    let closure = |x| x + y; // captures y by reference\n    let result = apply_twice_fn(closure, 5);\n    println!("Fn: {}", result); // 25\n}
\n\n

Example with FnMut

\n
fn apply_twice_mut<F>(mut f: F, value: i32) -> i32\nwhere\n    F: FnMut(i32) -> i32,\n{\n    f(f(value))\n}\n\nfn main() {\n    let mut counter = 0;\n    let mut closure = |x: i32| -> i32 {\n                

Blogs

Book Recommendations