0
votes

I have the following scenario in a Rust project:

struct Foo<'a> {
    stuff: &'a i32,
}

thus I'm telling the compiler that my Foo struct holds a reference to something and I have to give it a lifetime to make that work, fine.

Now, I declare this:

type FooFunc<'a> = &'a dyn Fn(&'a mut Foo<'a>) -> bool;

const funcs: [FooFunc; 4] = [
    &|f| { *f.stuff = 0; false },
    &|f| { *f.stuff = 1; true },
    &|f| { *f.stuff = 2; true },
    &|f| { *f.stuff = 3; true },
];

and try to call a closure from that const array inside a "method" of Foo:

impl<'a> Foo<'a> {
    fn bar(&mut self, i: usize) -> bool {
        funcs[i](self)
    }
}

Reasoning about lifetimes, this should be fine since the self reference to the Foo struct has a lifetime 'a (which sure thing doesn't outlive 'static, that is the lifetime of the closures) so the closures should be able to receive self as a parameter without any issue.

And the borrow checker sort of agrees with this, but still reports an error I don't understand:

error[E0308]: mismatched types
 --> src/main.rs:7:18
  |
7 |         funcs[i](self)
  |                  ^^^^ lifetime mismatch
  |
  = note: expected type `&'static mut Foo<'static>`
             found type `&'static mut Foo<'a>`
note: the lifetime 'a as defined on the impl at 5:6...
 --> src/main.rs:5:6
  |
5 | impl<'a> Foo<'a> {
  |      ^^
  = note: ...does not necessarily outlive the static lifetime

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
 --> src/main.rs:7:18
  |
7 |         funcs[i](self)
  |                  ^^^^
  |
  = note: ...the reference is valid for the static lifetime...
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the method body at 6:5
 --> src/main.rs:6:5
  |
6 | /     fn bar(&mut self, i: usize) -> bool {
7 | |         funcs[i](self)
8 | |     }
  | |_____^

It's telling me (in the note tags) that the lifetime 'a as defined on the impl at 5:6 does not necessarily outlive the static lifetime and that the reference is valid for the static lifetime but the borrowed content is only valid for the anonymous lifetime #1 defined on the method body at 6:5. While a compiler giving these kind of error messages is amazing, I still don't get what the problem is.

Talking about the second note (which is the one that sort of (tries to) explain the issue), I can't understand if the borrowed content is meant to be the const array or the self reference, but in either case:

  • if the "borrowed content" is the const array, why would that be a problem even if an anonymous lifetime that matches the function body has been assigned to it? The closure just has to operate on the reference that gets passed as a parameter and return by transferring ownership of the return value, and not by returning a reference with some weird lifetime that could cause problems.

  • if the "borrowed content" is the self reference, again, why would that be the problem? Okay, the self reference is no more 'a, but it's another one (let's call it 'b) which is "included" in 'a, thus shouldn't outlive 'static, right?

Sure thing I'm missing something here, any help would be appreciated.

Note: all the code in this post is just a stripped down version of the "architecture" I'm trying to implement - of course the logic you see in the example code could be easily implemented in a way simple manner, but that's not what I need. I would like to have a table of operations to do on my struct (thus the idea to make the elements closures that accept a &mut reference) and run these operations from a "method" of my struct. The actual type in the actual project is instead [Option<[FooFunc<'a>; 6]>; 256], so we're talking a pretty big bi-dimensional "table", which I imagine would become quite unpleasant to implement with match statements, especially considering I reuse FooFuncs very often.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=17034fe41c74d65d7a296ae812f19945

1
It does indeed help, thank you very much. I didn't think about using _ to phase out the lifetime in the type and that the lifetime associated to the type itself might have been the problem. I guess the lesson to learn is to try working with as few lifetimes specifiers as possible?gdelazzari

1 Answers

1
votes

The line type FooFunc<'a> = &'a dyn Fn(&'a mut Foo<'a>) -> bool; does not say "A FooFunc is a reference of any lifetime to a closure which takes a reference of any lifetime (to a Foo of the same lifetime)". Instead it says "A FooFunc<'a> is a reference with a specific lifetime 'a to a closure which takes a reference of that specific lifetime 'a (to a Foo of the same specific lifetime 'a), which is the same every time that closure is called."

Moreover, when you omit the lifetime in the item declaration ([FooFunc; 4]) it's inferred to be 'static. (This is because types themselves can't outlive their type parameters, but the item must be 'static, so the only valid lifetime parameter is also 'static.) So funcs is an array of references to closures which only accept &'static mut Foo<'static>.

What you probably want is type FooFunc<'r> = &'r for<'a, 'b> dyn Fn(&'a mut Foo<'b>) -> bool;, although this still fails due to mutability issues elsewhere. What that says is "A FooFunc<'r> is a reference of lifetime 'r to a closure which accepts references of any lifetime to Foos with any lifetime (the latter lifetime implicitly outlives the first lifetime)." 'r will still be inferred as 'static but that's okay because that's what you have.