Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a trait for closures results in bound/concrete lifetime mismatch

I want to implement a trait for closures of a specific type. Here is a minimal example (playground):

trait Foo {
    fn foo(&self, x: &u32);
}

impl<F> Foo for F
    where F: Fn(&u32)
{
    fn foo(&self, x: &u32) {
        self(x)
    }
}

fn main() {
    let _: &FnOnce(&u32) = &|x| {};   // works
    let _: &Foo          = &|x| {};   // doesn't work
}

It results in this error:

error: type mismatch resolving `for<'r> <[closure@<anon>:16:29: 16:35] as std::ops::FnOnce<(&'r u32,)>>::Output == ()`:
 expected bound lifetime parameter ,
    found concrete lifetime [--explain E0271]
  --> <anon>:16:28
   |>
16 |>     let _: &Foo          = &|x| {};
   |>                            ^^^^^^^
note: required because of the requirements on the impl of `Foo` for `[closure@<anon>:16:29: 16:35]`
note: required for the cast to the object type `Foo`

error: type mismatch: the type `[closure@<anon>:16:29: 16:35]` implements the trait `std::ops::Fn<(_,)>`, but the trait `for<'r> std::ops::Fn<(&'r u32,)>` is required (expected concrete lifetime, found bound lifetime parameter ) [--explain E0281]
  --> <anon>:16:28
   |>
16 |>     let _: &Foo          = &|x| {};
   |>                            ^^^^^^^
note: required because of the requirements on the impl of `Foo` for `[closure@<anon>:16:29: 16:35]`
note: required for the cast to the object type `Foo`

I already tried to explicitly add the HRTB to the where clause like this:

where F: for<'a> Fn(&'a u32)

But it didn't help. I also declared the lifetime on the impl block instead, like this:

impl<'a, F> Foo for F
    where F: Fn(&'a u32) { ... }

But this results in a lifetime error within the impl block. I think that those errors are right and the lifetime parameter can't be declared on the impl block.

How can I fix this example?

like image 831
Lukas Kalbertodt Avatar asked Sep 06 '16 19:09

Lukas Kalbertodt


2 Answers

Check out this part of the error:

[...] implements the trait std::ops::Fn<(_,)>, but the trait for<'r> std::ops::Fn<(&'r u32,)> is required

I think that basically there's not enough code to allow types to be properly inferred. Adding an explicit type annotation allows the example to be compiled:

let _: &Foo          = &|x: &u32| {};
like image 160
Shepmaster Avatar answered Nov 16 '22 06:11

Shepmaster


Here's a partial answer, starting with an interesting experiment:

trait Foo {
    fn foo(&self, x: &u32);
}

impl<F> Foo for F
    where F: Fn(&u32)
{
    fn foo(&self, x: &u32) {
        self(x)
    }
}

fn main() {
    let f1: &Fn(&u32) = &|_x| {}; 
    let f2: &Foo = &f1;
    // but this fails:
    // let f3: &Foo = &|_x| {};
    f2.foo(&3);
}

(Playground)

All I've done is change the FnOnce to Fn for consistency with the trait, and assign your first closure to a binding of type &Foo - and this one work.

This tells me that the trait itself is fine - it's a problem inferring the type of the closure when making the trait object. Going back to the error, we're told that the closure implements std::ops::Fn<(_,)>, but for<'r> std::ops::Fn<(&'r u32,)> is required. This means that the first thing you tried (adding the for<'r>... to the trait) didn't have any effect because the trait already requires this.

At this point I'm stuck - I don't think I understand the inference rules for closures in enough detail to see either why there's a difference, or how to make it work. I'm hoping someone will come and fill that in!

like image 41
Chris Emerson Avatar answered Nov 16 '22 06:11

Chris Emerson