'Cannot set default function closure [duplicate]

I am trying to create a method that takes an Option<impl Fn() -> ()> as a parameter, and, if this parameter is Some(func), stores func as a closure, but, if it is None, stores some default function as the closure. Specifically, my code is as follows:

fn foo() 
{
    println!("HI");
}

fn bar(baz: Option<impl Fn () -> ()>)
{
    let func = match baz {
        Some(func) => func,
        //None => foo,
        None => unimplemented!()
    };

    func();
}

pub fn main() {
    bar(Some(foo));
}

As-is, the code compiles, which indicates to me that foo is indeed something that should work with impl Fn () -> (). However, if I swap out the None arm of my match with the commented out None arm to set foo as my "default implentation", I get the following error:

error[E0308]: `match` arms have incompatible types
  --> <source>:10:17
   |
6  |   fn bar(baz: Option<impl Fn () -> ()>)
   |                      ---------------- this type parameter
7  |   {
8  |       let func = match baz {
   |  ________________-
9  | |         Some(func) => func,
   | |                       ---- this is found to be of type `impl Fn() -> ()`
10 | |         None => foo,
   | |                 ^^^ expected type parameter `impl Fn() -> ()`, found fn item
11 | |         //None => unimplemented!()
12 | |     };
   | |_____- `match` arms have incompatible types
   |
   = note: expected type `impl Fn() -> ()`
           found fn item `fn() {foo}`

error: aborting due to previous error

As far as I can tell from the error, it is saying that foo does not qualify as impl Fn() -> () within the function (though it did when passed in as a parameter). What am I missing here that causes this, and is there a way to accomplish what I want here (take an optional closure as a parameter, and use a default function as a closure if none provided)?



Solution 1:[1]

All the arms of a match statement must resolve to the exact same type.

With that in mind, let's replace impl Fn() -> () (which is also the same as impl Fn() BTW) with the generics that it represents:

fn bar<T>(baz: Option<T>) where T: Fn() {
  // ...
}

impl Trait (in a function parameter) is syntactic sugar for regular generics, so this is "what the compiler sees".

Then let's look at the match:

fn bar<T>(baz: Option<T>) where T: Fn() {
  let f = match baz {
    Some(f) => f,
    None => foo,
  };
}

This can't work, because the type of foo is a special thing called a "function item" (essentially a unique type), but the type of f is a generic parameter T: it could be any type imaginable (as long as it implements Fn()). Clearly, these might be different, so Rust rejects the match statement as "having incompatible types".

What you're looking for is a trait object (though this comes at the cost of a heap allocation and vtable dispatch):

fn bar(baz: Option<impl Fn()>) {
  let f: Box<dyn Fn()> = match baz {
    Some(f) => Box::new(f),
    None => Box::new(foo),
  };

  f();
}

Or, you can call the function immediately without storing in a variable:

fn bar(baz: Option<impl Fn()>) {
  match baz {
    Some(f) => f(),
    None => foo(),
  };
}

This is likely to be faster than the dyn Fn() approach above, though of course, if you are later storing it in a struct, this will not work.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 cameron1024