Need help getting lifetimes right

In essence, this is the setup I'm working with: (playground link)

trait Lifetime<'a> {
    type Item: 'a;

    fn take_by_ref(&'a self) -> Self::Item;
}

struct WithLifetime<'a> {
    a_ref: &'a ()
}

impl <'a> Lifetime<'a> for WithLifetime<'a> {
    type Item = &'a ();
    
    fn take_by_ref(&'a self) -> Self::Item { 
        self.a_ref
    }
}

struct Container<'a, T> {
    some_ref: &'a (),
    val: T
}

impl <'a, T> Container<'a, T>
where T: Lifetime<'a> + 'a {
    fn consume(self) {
        self.val.take_by_ref();
    }
}

fn main() {
    let val = WithLifetime { a_ref: &() };
    let container = Container {
        some_ref: &(),
        val
    };
    
    container.consume()
}

Anything I've tried so far has either caused the trait implementation or its usage to cause lifetime errors - this is the current one:

error[E0597]: `self.val` does not live long enough
  --> src/main.rs:27:9
   |
24 | impl <'a, T> Container<'a, T>
   |       -- lifetime `'a` defined here
...
27 |         self.val.take_by_ref();
   |         ^^^^^^^^--------------
   |         |
   |         borrowed value does not live long enough
   |         argument requires that `self.val` is borrowed for `'a`
28 |     }
   |     - `self.val` dropped here while still borrowed

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.

I'm fairly certain that the lifetime on the trait is necessary - it's pretty much analogous to rayon's IntoParallelRefIterator.
I'm assuming the error is due to the lifetime annotation requiring self.val to live past the function, even though it is dropped at the end of it... I'm just not sure how to express that correctly.
Or maybe what I'm trying to do is simply impossible and I need to redesign everything?

Also, it's probably fairly harmless to use uns*fe here, but... you know... I'd rather not.

Your impl block is certainly wrong. You want to implement the Lifetimes trait for WithLifetime not just with the lifetime on WithLifetime, but also any shorter lifetime.

impl<'a: 'b, 'b> Lifetime<'b> for WithLifetime<'a> {
    type Item = &'a ();
    
    fn take_by_ref(&'b self) -> Self::Item { 
        self.a_ref
    }
}

You need to do something similar on the other impl too. Here's something that compiles:

impl<'a, T> Container<'a, T>
where
    T: for<'b> Lifetime<'b>
{
    fn consume(self) {
        self.val.take_by_ref();
    }
}
3 Likes

Thank you for your help! I realise now that I actually had something similar to this solution before - the problem was that I had another bounded type on Container that referred to the other. Something like:

struct Container<'a, T, F> {
    some_ref: &'a (),
    val: T,
    f: F
}

impl<'a, T, F> Container<'a, T, F>
where
    T: Lifetime<'a>,
    F: Fn(<T as Lifetime<'a>>::Item)
{
    fn consume(self) {
        self.val.take_by_ref();
    }
}

Now, using the for notation works for bounding one type. But what do I do for the second one?
This code doesn't work:

struct Container<'a, T, F> {
    some_ref: &'a (),
    val: T,
    f: F
}

impl<'a, T, F> Container<'a, T, F>
where
    T: for<'b> Lifetime<'b>,
    F: for<'b> Fn(<T as Lifetime<'b>>::Item)
{
    fn consume(self) {
        self.val.take_by_ref();
    }
}

fn main() {
    let val = WithLifetime { a_ref: &() };
    let container = Container {
        some_ref: &(),
        val,
        f: |_| ()
    };
    
    container.consume()
}
error[E0599]: no method named `consume` found for struct `Container<'_, WithLifetime<'_>, [closure@src/main.rs:40:12: 40:18]>` in the current scope
  --> src/main.rs:43:15
   |
19 | struct Container<'a, T, F> {
   | -------------------------- method `consume` not found for this
...
40 |         f: |_| ()
   |            ------
   |            |
   |            doesn't satisfy `<_ as std::ops::FnOnce<(<WithLifetime<'_> as Lifetime<'b>>::Item,)>>::Output = ()`
   |            doesn't satisfy `_: std::ops::Fn<(<WithLifetime<'_> as Lifetime<'b>>::Item,)>`
...
43 |     container.consume()
   |               ^^^^^^^ method not found in `Container<'_, WithLifetime<'_>, [closure@src/main.rs:40:12: 40:18]>`
   |
   = note: the method `consume` exists but the following trait bounds were not satisfied:
           `<[closure@src/main.rs:40:12: 40:18] as std::ops::FnOnce<(<WithLifetime<'_> as Lifetime<'b>>::Item,)>>::Output = ()`
           `[closure@src/main.rs:40:12: 40:18]: std::ops::Fn<(<WithLifetime<'_> as Lifetime<'b>>::Item,)>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0599`.

And as far as I know, there's now way to have a(n) HRTB apply to two types, is there?

(updated playground)

Turns out you can make the code compile with this impl:

impl<'a, T, F> Container<'a, T, F>
where
    T: for<'b> Lifetime<'b> + 'a,
    F: Fn(<T as Lifetime<'a>>::Item)
{
    fn consume(self) {
        self.val.take_by_ref();
    }
}

But if you then try to apply the function, it stops working again:

impl<'a, T, F> Container<'a, T, F>
where
    T: for<'b> Lifetime<'b> + 'a,
    F: Fn(<T as Lifetime<'a>>::Item)
{
    fn consume(self) {
        (self.f)(self.val.take_by_ref())
    }
}
error[E0597]: `self.val` does not live long enough
  --> src/main.rs:31:18
   |
25 | impl<'a, T, F> Container<'a, T, F>
   |      -- lifetime `'a` defined here
...
31 |         (self.f)(self.val.take_by_ref())
   |                  ^^^^^^^^--------------
   |                  |
   |                  borrowed value does not live long enough
   |                  argument requires that `self.val` is borrowed for `'a`
32 |     }
   |     - `self.val` dropped here while still borrowed

error: aborting due to previous error

For more information about this error, try `rustc --explain E0597`.

If you try to use a not fully qualified path like this:

where
    T: for<'b> Lifetime<'b> + 'a,
    F: Fn(T::Item)

... the compiler tells you to use a fully qualified path with inferred lifetimes: <T as Lifetime<'_>>::Item.
But upon doing that, you just get this again:

error[E0599]: no method named `consume` found for struct `Container<'_, WithLifetime<'_>, [closure@src/main.rs:40:12: 40:22]>` in the current scope
  --> src/main.rs:43:15
   |
19 | struct Container<'a, T, F> {
   | -------------------------- method `consume` not found for this
...
40 |         f: |_: &()|()
   |            ----------
   |            |
   |            doesn't satisfy `<_ as std::ops::FnOnce<(<WithLifetime<'_> as Lifetime<'_>>::Item,)>>::Output = ()`
   |            doesn't satisfy `_: std::ops::Fn<(<WithLifetime<'_> as Lifetime<'_>>::Item,)>`
...
43 |     container.consume()
   |               ^^^^^^^ method not found in `Container<'_, WithLifetime<'_>, [closure@src/main.rs:40:12: 40:22]>`
   |
   = note: the method `consume` exists but the following trait bounds were not satisfied:
           `<[closure@src/main.rs:40:12: 40:22] as std::ops::FnOnce<(<WithLifetime<'_> as Lifetime<'_>>::Item,)>>::Output = ()`
           `[closure@src/main.rs:40:12: 40:22]: std::ops::Fn<(<WithLifetime<'_> as Lifetime<'_>>::Item,)>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0599`.

Also, something weird I've noticed: The <T as Lifetime<'_>>::Item trick works with the Fn family of traits, but does not appear to work with a normal trait like trait Something<T>. In that case you get:

error[E0637]: `'_` cannot be used here
  --> src/main.rs:31:33
   |
31 |     F: Something<<T as Lifetime<'_>>::Item>
   |                                 ^^ `'_` is a reserved lifetime name

I'm really rather lost here...
(current playground link)