Differences between calling closures from Box<FnOnce()> and Box<Fn()>

The following code works fine.

type Multiplier = Fn(i32) -> i32;

fn multiplier(c: i32) -> Box<Multiplier> {
    Box::new(move |x| c * x)
}

fn main() {
    let m = multiplier(3);
    println!("{}", m(4));
}

However, if I change Multiplier to FnOnce(i32) -> i32 (listed in the following code), the compiler raises an error indicating that I can’t move out an unsized value. Why!?

type Multiplier = FnOnce(i32) -> i32;

fn multiplier(c: i32) -> Box<Multiplier> {
    Box::new(move |x| c * x)
}

fn main() {
    let m = multiplier(3);
    println!("{}", m(4));
}

See the docs in https://doc.rust-lang.org/std/boxed/trait.FnBox.html, which mentions the reason.

The tl;dr is that FnOnce consumes self when called. If you have a such a boxed closure, you need to move that value out of the box to call it. But moving the value out of the box yields an unsized type (i.e. a bare trait, we don’t know the exact type since it was boxed), you get the error.

FnBox is a (unstable) workaround that makes use of a self: Box<Self> receiver, which doesn’t require taking the inner (type erased) value out.

You can also replicate FnBox in your own code (without the associated closure call sugar, however) by defining a similar trait.

2 Likes

Thanks! :star_struck:

Just a note, but you don’t need to box closures.

This works.

fn multiplier(c: i32) -> impl FnOnce(i32) -> i32 {
    move |x| c * x
}
3 Likes