Closures

The FnOnce Trait

The FnOnce trait in std::ops is the base trait implemented by every closure and function, representing a callable that consumes itself by value (self) when invoked and therefore is only guaranteed to be callable once.

Conceptual Definition

pub trait FnOnce<Args> {
    type Output;

    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

Because FnOnce::call_once takes self by value, a closure that moves a non-Copy value out of its captured environment can safely return or drop that value during its single execution without leaving the closure struct in a partially moved, invalid state for a second call.

Closures That Only Implement FnOnce

A closure implements only FnOnce (and neither FnMut nor Fn) when its body moves ownership out of a captured variable:

fn execute_task<F, T>(task: F) -> T
where
    F: FnOnce() -> T,
{
    task()
}

fn main() {
    let buffer = vec![10, 20, 30];

    // `.into_iter()` consumes `buffer` by value, so `sum_and_drop` is `FnOnce` only:
    let sum_and_drop = || {
        buffer.into_iter().sum::<i32>()
    };

    let result = execute_task(sum_and_drop);
    println!("Result: {result}");
}
Result: 60

Calling an FnOnce-Only Closure Twice Fails (E0382)

Because invoking an FnOnce-only closure consumes the closure value on the first call, calling it a second time fails with a use-after-move error (E0382):

// ❌ does not compile
fn main() {
    let name = String::from("ephemeral");

    let consume_name = || {
        drop(name);
    };

    consume_name();
    consume_name(); // Error: `consume_name` was moved and consumed in the first call
}
error[E0382]: use of moved value: `consume_name`
  --> src/main.rs:10:5
   |
5  |     let consume_name = || {
   |         ------------ move occurs because `consume_name` has type `{closure@src/main.rs:5:24: 5:26}`, which does not implement the `Copy` trait
...
9  |     consume_name();
   |     -------------- `consume_name` moved due to this call
10 |     consume_name();
   |     ^^^^^^^^^^^^ value used here after move
   |
note: closure cannot be invoked more than once because it moves the variable `name` out of its environment
  --> src/main.rs:6:14
   |
6  |         drop(name);
   |              ^^^^

Standard Library APIs Using FnOnce

Single-shot callbacks in the standard library always bound their parameter by FnOnce so callers can freely move owned values out of their environment:

  • Option::unwrap_or_else(f: impl FnOnce() -> T)
  • Option::map(f: impl FnOnce(T) -> U)
  • Result::map_err(op: impl FnOnce(E) -> F)
  • std::thread::spawn(f: impl FnOnce() -> T + Send + 'static)