Error

"expected a closure that implements Fn but this closure only implements FnOnce" — How to Fix

This error occurs because the function or trait you are passing the closure to requires it to be callable multiple times (`Fn`), but your closure captures a variable by move, making it callable only once (`FnOnce`).

The closure that burns after one use

You write a closure to process some data. You pass it to a function that calls it in a loop. The compiler rejects you with E0277 (trait bound not satisfied), complaining that your closure only implements FnOnce but the function expects Fn. You stare at the code. The closure looks harmless. It just prints a string or reads a config value. Why can't it run twice?

The issue is ownership. Your closure captured a variable by moving it into the closure's environment. Moving a non-Copy value consumes that value. The closure now owns the data, and the compiler assumes the closure might consume it on the first call. Once consumed, the data is gone. The closure can't run again. It is a one-shot closure. The function you passed it to needs a closure that can be called repeatedly. The contract is broken.

The three closure traits

Rust describes closure capabilities with three traits. Every closure implements at least one of them, and often more. The traits form a hierarchy based on what the closure does to its captured variables.

FnOnce is the base trait. It means the closure can be called once. It might consume its captured variables. After the call, the closure is exhausted. Think of a match. You strike it, it burns, it's gone.

FnMut means the closure can be called multiple times, but it might mutate its captured variables. The closure stays alive, but its internal state can change. Think of a whiteboard. You can write on it over and over, changing what's on the board, but the board itself remains.

Fn means the closure can be called multiple times and does not mutate its captured variables. The closure is stateless with respect to its environment. Think of a light switch. You can flip it on and off as many times as you want, and the switch doesn't change.

The hierarchy matters for function arguments. Fn is a subtrait of FnMut, which is a subtrait of FnOnce. If a function accepts FnOnce, you can pass any closure. If it accepts FnMut, you can pass FnMut or Fn. If it accepts Fn, you can only pass Fn. You cannot pass a FnOnce closure where Fn is expected. The compiler enforces this because FnOnce might consume data that Fn promises to preserve.

How Rust decides the trait

Rust infers which traits your closure implements based on how it captures variables. The capture mode determines the trait. Rust is conservative. It picks the weakest capture mode that satisfies the closure body.

If the closure only reads a variable, Rust captures it by immutable reference. The closure implements Fn.

If the closure mutates a variable, Rust captures it by mutable reference. The closure implements FnMut.

If the closure consumes a variable, or if you explicitly write the move keyword, Rust captures by value. The closure implements FnOnce for non-Copy types.

This inference is where the confusion starts. Rust prefers references. You rarely get a FnOnce closure by accident. The error almost always appears when you add move explicitly, or when the closure body calls a method that takes ownership of a captured value.

The move keyword forces the closure to take ownership of all captured variables. It overrides the inference. Even if the closure only reads the data, move copies or moves the value into the closure. For a String, that's a move. The closure owns the String. The compiler marks the closure as FnOnce because it holds a unique owner of a non-Copy type. The closure could theoretically be called multiple times if it only reads, but the trait implementation is locked to FnOnce by the move capture. The compiler does not analyze the body to upgrade the trait. The capture mode dictates the trait.

Minimal example

Here is the error in action. The function apply_twice requires Fn. The closure captures a String by move.

fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) {
    // f must be callable twice.
    let r1 = f(x);
    let r2 = f(x);
    println!("Results: {}, {}", r1, r2);
}

fn main() {
    let data = String::from("hello");

    // ERROR: E0277. The closure captures `data` by move.
    // It implements FnOnce, but apply_twice requires Fn.
    let bad_closure = move |x| {
        println!("{} {}", data, x);
        x
    };

    apply_twice(bad_closure, 1);
}

The move keyword forces data into the closure. String is not Copy. The closure owns data. It implements FnOnce. The compiler rejects the call.

The fix is to remove move. Rust infers an immutable borrow. The closure captures &data. It implements Fn.

fn main() {
    let data = String::from("hello");

    // FIX: No `move`. Rust infers a borrow.
    // The closure captures `&data` and implements Fn.
    let good_closure = |x| {
        println!("{} {}", data, x);
        x
    };

    apply_twice(good_closure, 1);
}

If you need to mutate the captured variable, you get FnMut. That won't satisfy Fn either. You need FnMut in the function signature, or you need interior mutability.

fn main() {
    let mut counter = 0;

    // This closure captures `&mut counter`.
    // It implements FnMut, not Fn.
    let mut_closure = |x| {
        counter += 1;
        println!("Count: {}, Input: {}", counter, x);
        x
    };

    // ERROR: apply_twice requires Fn, but mut_closure is FnMut.
    // apply_twice(mut_closure, 1);
}

Don't fight the compiler here. Reach for RefCell if you need mutation inside an Fn closure, or change the function signature to accept FnMut.

Realistic scenario: retry logic

You are building a retry mechanism for a network call. You have a configuration string. You want to retry the connection up to three times.

fn retry<F: Fn() -> Result<(), String>>(f: F, max_attempts: u32) {
    for attempt in 0..max_attempts {
        match f() {
            Ok(()) => return,
            Err(e) => println!("Attempt {} failed: {}", attempt + 1, e),
        }
    }
    panic!("All attempts failed");
}

fn main() {
    let config = String::from("https://api.example.com");

    // You want to move config into the closure because the retry
    // function might be called later, or you want to isolate the data.
    let op = move || {
        println!("Connecting to {}", config);
        // Simulate connection logic
        Ok(())
    };

    // ERROR: E0277. `op` is FnOnce due to `move`, but retry needs Fn.
    retry(op, 3);
}

The move keyword is tempting here. You want the closure to own the config. But retry calls the closure in a loop. It needs Fn. The move capture breaks the contract.

You have three paths. Remove move if the lifetime allows. Use Rc to share ownership. Or change retry to accept FnOnce and recreate the closure each iteration, though that's inefficient.

The cleanest fix for shared data is Rc. Clone the reference-counted pointer into the closure. The closure owns a pointer, not the data. The pointer is Copy-like in behavior for the closure trait.

use std::rc::Rc;

fn main() {
    let config = Rc::new(String::from("https://api.example.com"));

    // Clone the Rc into the closure.
    // The closure owns an Rc, which can be called multiple times.
    let op = move || {
        println!("Connecting to {}", config);
        Ok(())
    };

    retry(op, 3);
}

Convention aside: use Rc::clone(&config) instead of config.clone(). Both compile. The explicit form signals to readers that you are cloning a reference count, not deep-copying the string. It prevents confusion.

Pitfalls and edge cases

Copy types behave differently. If you capture an i32 by move, the closure still implements Fn. The value is copied into the closure. The closure owns a copy. It can be called repeatedly. The FnOnce restriction only applies to non-Copy types.

Lifetimes can block the fix. If you remove move to get a borrow, the closure now has a lifetime bound. It cannot outlive the borrowed data. If the function you're passing the closure to requires 'static lifetime, the borrow fails. You must use move with Rc or Arc to satisfy 'static while keeping Fn.

The error message can be misleading. Sometimes the compiler says "expected Fn, found FnMut". That's a different problem. You're mutating captured state, but the function requires a pure closure. The fix is the same pattern: use RefCell for interior mutability, or change the function signature.

Counter-intuitive but true: the more you use move, the harder it is to satisfy Fn bounds. move is a sledgehammer. It solves lifetime issues by taking ownership, but it destroys reusability for non-Copy data. Reach for borrows first. Use move only when the data must be owned or when sending to another thread.

Decision matrix

Use an immutable borrow (&data) when the closure only reads the variable and the variable lives long enough. This gives you Fn, allowing multiple calls without ownership transfer.

Use a mutable borrow (&mut data) when the closure modifies the variable. This gives you FnMut, allowing multiple calls with state changes. Ensure the function accepts FnMut.

Use move when the closure must own the data, such as when sending it to another thread or when the data is a temporary that would otherwise be dropped. This gives you FnOnce for non-Copy types, restricting the closure to a single call.

Use Rc<T> when multiple owners need to share the data and the closure must be called multiple times. Clone the Rc into the closure to share ownership without moving the underlying value. This preserves Fn capability even with move.

Use RefCell<T> when you need interior mutability inside a closure that must implement Fn. Wrap the data in RefCell so the closure can mutate it through the reference while still satisfying the immutable borrow contract.

Where to go next