DEV Community

Cover image for How Rust Handles Closures: Fn, FnMut, and FnOnce
Leapcell
Leapcell

Posted on

How Rust Handles Closures: Fn, FnMut, and FnOnce

Cover

In the Rust programming language, closures are a powerful and flexible feature that allows you to define anonymous functions and capture variables from their surrounding environment. Rust’s closure system is defined by three core traits—Fn, FnMut, and FnOnce—that determine how closures interact with captured variables, how many times they can be called, and whether they can modify their environment. Understanding these traits is crucial for mastering Rust’s closure mechanism and writing efficient, safe code.

This article provides a detailed introduction to the three traits—Fn, FnMut, and FnOnce—including their definitions, use cases, methods, applicable scenarios, and best practices, along with code examples to help you comprehensively learn the relevant concepts.

What Are Fn, FnMut, and FnOnce?

Fn, FnMut, and FnOnce are three traits defined in Rust’s standard library to describe the behavior of closures (or any callable objects). Their primary difference lies in how they access captured variables and the ownership rules when they are called:

  • FnOnce: Indicates that a closure can be called once. After being called, the closure itself is consumed and can no longer be used.
  • FnMut: Indicates that a closure can be called multiple times and can modify captured variables when invoked.
  • Fn: Indicates that a closure can be called multiple times and only reads captured variables without modifying them.

There is an inheritance relationship among these three traits:

  • Fn inherits from FnMut, and FnMut inherits from FnOnce.
  • Therefore, if a closure implements Fn, it also automatically implements FnMut and FnOnce; if it implements FnMut, it also implements FnOnce.

Definition of Each Trait

FnOnce

The FnOnce trait defines a call_once method with the following signature:

pub trait FnOnce<Args> {
    type Output;
    fn call_once(self, args: Args) -> Self::Output;
}
Enter fullscreen mode Exit fullscreen mode
  • Characteristics: call_once takes self (instead of &self or &mut self), meaning that when the closure is called, it transfers ownership of itself and can therefore only be invoked once.
  • Use cases: Suitable for scenarios where a closure needs to move captured variables or perform a one-time operation.

FnMut

The FnMut trait defines a call_mut method with the following signature:

pub trait FnMut<Args>: FnOnce<Args> {
    fn call_mut(&mut self, args: Args) -> Self::Output;
}
Enter fullscreen mode Exit fullscreen mode
  • Characteristics: call_mut takes &mut self, allowing the closure to modify its internal state or captured variables when invoked, and it can be called multiple times.
  • Use cases: Suitable for scenarios where a closure needs to modify its environment across multiple calls.

Fn

The Fn trait defines a call method with the following signature:

pub trait Fn<Args>: FnMut<Args> {
    fn call(&self, args: Args) -> Self::Output;
}
Enter fullscreen mode Exit fullscreen mode
  • Characteristics: call takes &self, meaning that the closure only immutably borrows itself and the captured variables. It can be called multiple times without modifying the environment.
  • Use cases: Suitable for scenarios where a closure needs to be called multiple times and only reads data.

How Closures Implement These Traits

Rust’s compiler automatically determines which traits a closure implements based on how it uses captured variables. A closure can capture variables in three ways:

  • By value (move): The closure takes ownership of the variable.
  • By mutable reference (&mut): The closure captures a mutable reference to the variable.
  • By immutable reference (&): The closure captures an immutable reference to the variable.

The implemented trait depends on how the captured variable is used:

  • Implements only FnOnce: The closure moves the captured variable.
  • Implements FnMut and FnOnce: The closure modifies the captured variable.
  • Implements Fn, FnMut, and FnOnce: The closure only reads the captured variable.

Code Examples

Closure Implementing FnOnce

fn main() {
    let s = String::from("hello");
    let closure = move || {
        drop(s); // Moves s and drops it
    };
    closure(); // Called once
    // closure(); // Error: Closure has been consumed
}
Enter fullscreen mode Exit fullscreen mode

Explanation: The closure captures s by move, taking ownership of it and dropping it when called. Since s is moved, the closure can only be called once, making it implement only FnOnce.

Closure Implementing FnMut

fn main() {
    let mut s = String::from("hello");
    let mut closure = || {
        s.push_str(" world"); // Modifies s
    };
    closure(); // First call
    closure(); // Second call
    println!("{}", s); // Outputs "hello world world"
}
Enter fullscreen mode Exit fullscreen mode

Explanation: The closure captures s by mutable reference and modifies it with each call. Since it needs to modify its environment, it implements FnMut and FnOnce.

Closure Implementing Fn

fn main() {
    let s = String::from("hello");
    let closure = || {
        println!("{}", s); // Reads s without modification
    };
    closure(); // First call
    closure(); // Second call
}
Enter fullscreen mode Exit fullscreen mode

Explanation: The closure captures s by immutable reference and only reads it without modification. Therefore, it implements Fn, FnMut, and FnOnce.

Using These Traits in Function Parameters

Closures can be passed as arguments to functions, and functions need to specify the required closure behavior using trait bounds.

Using FnOnce

fn call_once<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

fn main() {
    let s = String::from("hello");
    call_once(move || {
        drop(s);
    });
}
Enter fullscreen mode Exit fullscreen mode

Explanation: call_once accepts an FnOnce closure and calls it once, making it suitable for closures that move captured variables.

Using FnMut

fn call_mut<F>(mut f: F)
where
    F: FnMut(),
{
    f();
    f();
}

fn main() {
    let mut s = String::from("hello");
    call_mut(|| {
        s.push_str(" world");
    });
    println!("{}", s); // Outputs "hello world world"
}
Enter fullscreen mode Exit fullscreen mode

Explanation: call_mut accepts an FnMut closure and calls it twice. The closure can modify the captured variable. Note that f must be declared as mut.

Using Fn

fn call_fn<F>(f: F)
where
    F: Fn(),
{
    f();
    f();
}

fn main() {
    let s = String::from("hello");
    call_fn(|| {
        println!("{}", s);
    });
}
Enter fullscreen mode Exit fullscreen mode

Explanation: call_fn accepts an Fn closure and calls it twice. The closure only reads the captured variable.

When to Use Each Trait?

Choosing the right trait depends on the behavior required for the closure:

FnOnce

  • Use case: The closure is called only once or needs to move captured variables.
  • Example: A one-time operation that transfers ownership.

FnMut

  • Use case: The closure needs to be called multiple times and modify captured variables.
  • Example: A counter or state update.

Fn

  • Use case: The closure needs to be called multiple times and only reads captured variables.
  • Example: Logging or data queries.

When designing functions, selecting the most permissive trait increases flexibility. For example, FnOnce accepts all closures but limits invocation, while Fn allows multiple calls but requires immutability.

Best Practices

  • Prefer Fn: If a closure doesn’t need to modify variables, use Fn for maximum compatibility.
  • Use FnMut when modification is needed: Choose FnMut when a closure needs to update state.
  • Use FnOnce for single-use closures: If a closure moves variables or performs a one-time task, use FnOnce.
  • Choose the right trait for APIs: Use FnOnce for single-use calls, Fn for multiple read-only calls, and FnMut for multiple calls with modifications.
  • Be mindful of lifetimes: Ensure captured variables live long enough to avoid borrow errors.

We are Leapcell, your top choice for hosting Rust projects.

Leapcell

Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:

Multi-Language Support

  • Develop with Node.js, Python, Go, or Rust.

Deploy unlimited projects for free

  • pay only for usage — no requests, no charges.

Unbeatable Cost Efficiency

  • Pay-as-you-go with no idle charges.
  • Example: $25 supports 6.94M requests at a 60ms average response time.

Streamlined Developer Experience

  • Intuitive UI for effortless setup.
  • Fully automated CI/CD pipelines and GitOps integration.
  • Real-time metrics and logging for actionable insights.

Effortless Scalability and High Performance

  • Auto-scaling to handle high concurrency with ease.
  • Zero operational overhead — just focus on building.

Explore more in the Documentation!

Try Leapcell

Follow us on X: @LeapcellHQ


Read on our blog

Top comments (0)