Expected closure, found a different closure

Hi !

I don't understand the following error :

enum Foo {
    Bar(usize),
    Baz(usize),
}

pub fn main() {
    for (a, b) in [
        //
        (42, |v| Foo::Bar(v)),
        //
        (42, |v| Foo::Baz(v)),
    ] {
        //
    }
}

Error :

error[E0308]: mismatched types
  --> src/main.rs:11:14
   |
9  |         (42, |v| Foo::Bar(v)),
   |              --- the expected closure
10 |         //
11 |         (42, |v| Foo::Baz(v)),
   |              ^^^^^^^^^^^^^^^ expected closure, found a different closure
   |
   = note: expected closure `[closure@src/main.rs:9:14: 9:17]`
              found closure `[closure@src/main.rs:11:14: 11:17]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object

For more information about this error, try `rustc --explain E0308`.
error: could not compile `demo` due to previous error

Putting closure in a box seems to change nothing :

enum Foo {
    Bar(usize),
    Baz(usize),
}

pub fn main() {
    let a: i32;
    let b: Box<dyn FnOnce(i32) -> Foo>;
    for (a, b) in [
        //
        (42, Box::new(|v| Foo::Bar(v))),
        //
        (42, Box::new(|v| Foo::Baz(v))),
    ] {
        //
    }
}

Read rustc --explain E0308 do not help me.

How can I make this list of closure type (or acceptable) ?

Closures all have their own, distinct type. These particular closures seem to capture nothing, so you could coerce them to a function pointer using as fn(_) -> _ to make them into the same type.

By the way, |arg| other_function(arg) is redundant. There's no need to create a closure if you already have a function in the first place; just use the function itself: Playground

1 Like

This compiles:

enum Foo {
    Bar(usize),
    Baz(usize),
}

pub fn main() {
    let iter: [(i32, Box<dyn FnOnce(i32) -> Foo>); 2] = [
        (42, Box::new(|v| Foo::Bar(v.try_into().unwrap()))),
        (42, Box::new(|v| Foo::Baz(v.try_into().unwrap()))),
    ];
    
    for (a, b) in iter {
        //
    }
}

Playground.

The and/or is slightly too unspecific in the error message at hand. The important part of that suggestion is the trait object; the boxing ist just also generally needed for trait objects, though in short-lived cases, working with references instead of Boxes might work and save an allocation. (I assume that’s where the “or” comes from in that error message in the first place.)

To solve the problem at hand by combining boxing and trait objects, the first thing beyond your approach of trying boxing is to make sure to put the whole closure into a box, not just the return value.

So something like Box::new(|v| Foo::Bar(v)), instead of the |v| Box::new(Foo::Bar(v)) you had.

Then, to use the trait object type, putting an (ultimately unrelated but identically named) local variable b with Box<dyn FnOnce(i32) -> Foo> signature does not help. And without signatures, the compiler still complains.

It’s necessary to convinve the compiler to coerce the boxed closure into a boxed trait object, at least explicitly for the first array element. After that, type inference will help do the rest.

enum Foo {
    Bar(usize),
    Baz(usize),
}

pub fn main() {
    for (a, b) in [
        //
        (42, Box::new(|v| Foo::Bar(v)) as Box<dyn FnOnce(_) -> _>),
        //
        (42, Box::new(|v| Foo::Baz(v))),
    ] {
        //
    }
}

As you can see, it isn’t even necessary in this case to write down the exact function signature into the FnOnce trait bound, but you might prefer to write it if you want to be more explicit.

If the goal is to avoid the “asymmetry” of specifying a type only on the first element, to name just one alternative that comes to mind, it’s also quite possible to write down the array type on a local variable first.

pub fn main() {
    let array: [(i32, Box<dyn FnOnce(_) -> _>); 2] = [
        //
        (42, Box::new(|v| Foo::Bar(v))),
        //
        (42, Box::new(|v| Foo::Baz(v))),
    ];
    for (a, b) in array {
        //
    }
}

As @H2CO3 mentioned above, in case the functions don’t capture any variables, you could also use function pointers instead; or, it that’s not good for your use-case depending what the use-case looks like, possibly you might want something like FnMut or Fn instead, and you might possibly want to add a Send and/or Sync bound. E.g. something that looks like Box<dyn FnMut(_) -> _ + Send>, to show just one example of ways the trait object type could be changed. Don’t worry much about the type at first though, it’s going to be an easy re-factor in case you only figure out the precise requirements at a later time.


Edit: I didn’t even quite expect this to work, but apparently, it’s also an option to use as […array type…] after the array directly to make this compile. For example

pub fn main() {
    for (a, b) in [
        //
        (42, Box::new(|v| Foo::Bar(v))),
        //
        (42, Box::new(|v| Foo::Baz(v))),
    ] as [(_, Box<dyn FnOnce(_) -> _>); 2]
    {
        //
    }
}

or for example

pub fn main() {
    for (a, b) in [
        //
        (42, Foo::Bar),
        //
        (42, Foo::Baz),
    ] as [(_, fn(_) -> _; 2]
    {
        //
    }
}

Not that that’s necessarily nicer-looking than defining a local variable for the array, but it does list yet another approach of writing a type signature hint that helps the compiler sufficiently.

2 Likes

I learned a lot today. Thanks !

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.