Test
Test

Reputation: 1720

Correct Function Type for temporary value

What is the correct function type for a "callback" function called inside a function?

fn or_else(value: u32, f: ???) -> u32 {
    if (value == 0) {
        f()
    } else {
        value
    }
}

Example usage:

or_else(0, || { 3 })

What is rust angry when I use f: &dyn FnOnce() -> u32?

Error:

cannot move a value of type dyn FnOnce() -> u32: the size of dyn FnOnce() -> u32 cannot be statically determinedrustcE0161
cannot move out of `*f` which is behind a shared reference
move occurs because `*f` has type `dyn FnOnce() -> u32`, which does not implement the `Copy` trait

I have trouble understanding E0161.

Upvotes: 0

Views: 78

Answers (1)

Kevin Reid
Kevin Reid

Reputation: 43753

The most general way to accept a function, in this case, is to make your function generic, using either of these syntaxes:

fn or_else(value: u32, f: impl FnOnce() -> u32) -> u32 {
fn or_else<F: FnOnce() -> u32>(value: u32, f: F) -> u32 {

This is saying: f can be of any type the caller cares to provide, as long as that type implements FnOnce (is a function that can be called once).

What is rust angry when I use f: &dyn FnOnce() -> u32?

FnOnce is named after the idea that it can only be called once, but what it actually means is that the function value is consumed (moved) by calling it. Thus, once you call the function, you don't have it any more.

But if you have &F then you are not allowed to move the F value, because you don't own it. So, it is impossible to call a FnOnce by reference. (Similarly, a FnMut requires a mutable reference, and cannot be called with an immutable reference.)

So, the type &dyn FnOnce is useless — it can exist, but it can never be called. If you want to use dyn (which can be a reasonable choice) then you have to use &dyn Fn or &mut dyn FnMut.

But, for a general-purpose function taking a callback, it is usually best to use generics instead of dyn. This is because:

  • it allows you to accept FnOnce, the most general function trait
  • it allows the function to be passed by value or by reference, if that is possible for the specific function, because references to Fn/FnMut functions also implement Fn/FnMut themselves
  • it allows the compiler to inline the callback function so the code can be optimized as a whole

Upvotes: 3

Related Questions