Return generic tuples with multiple lifetimes?

To illustrate the problem,

There is a struct called Bar

struct Bar { data: [u8; 8] }

And trait called Foo,

trait Foo {
    fn foo<'a>(bar: &'a Bar) -> Self 
    where 
         Self: 'a;
}

As you can see, My intention is to get a value or a reference from Bar for Example (playground)

impl Foo for &u8 {
    //3      ^  but, the lifetime must be valid for the lifetime `'_`
    fn foo<'a>(bar: &'a Bar) -> Self where Self: 'a { 
    //1    ^^  first, the lifetime cannot outlive the lifetime `'a`
        &bar.data[0] // Error: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
    //2  ^^^^^^^^ ..so that reference does not outlive borrowed content (#2)
    }
}

This error is kinda new for me... Also massage is a bit confusing... what's the error massage is saying ? But anyway, lets continue...

So I modify Foo trait a bit...

trait Foo<'a> {
    fn foo(bar: &'a Bar) -> Self 
    where 
         Self: 'a;
}
impl<'a> Foo<'a> for &'a u8 {
    fn foo(bar: &'a Bar) -> Self {
        &bar.data[0]
    }
}

It worked...

But I have no idea, How to resolve this, Playground

impl<'a, 'b, A: Foo<'a>, B: Foo<'b>> Foo<'_> for (A, B) {
    fn foo(bar: &Bar) -> Self {
        todo!()
        // (A::foo(bar), B::foo(bar))
    }
}

The lifetime complicated a Lot!

I'm running late for something and don't have time to explain it (sorry), but how about this?::

impl<'a, 'b, 'c, A: Foo<'a>, B: Foo<'b>> Foo<'c> for (A, B)
where
    'c: 'a + 'b,
    Self: 'a + 'b,
{
    fn foo(bar: &'c Bar) -> Self {
        (A::foo(bar), B::foo(bar))
    }
}
2 Likes

So we should use a placeholder like 'c, and then also define like Self: 'c, where c: 'a + 'b?

Let me try to explain this part first. This implementation:

impl Foo for &u8 { /* ... */ }

Is short for:

impl<'any> Foo for &'any u8 { /* ... */ }

And when you omit a lifetime in an impl header like this, or use '_, you're basically saying "the lifetime doesn't matter, this should work the same no matter what the lifetime is." But that isn't true here, which we can see if we pick the right specific lifetime, say 'static:

impl Foo for &'static u8 {
    fn foo<'a>(bar: &'a Bar) -> Self where Self: 'a {
        &bar.data[0]
    }
}

&'static u8: 'a for any 'a, so that bound isn't doing anything -- and we're saying that we can return a &'static u8 for any 'a that the caller of foo chooses. But your implementation tries to return something that you can only borrow for 'a at most.

You really wanted a bound that went in the other direction, which can be tricky to express.

With your updated trait, you're trying to get a &'a u8 from a &'a Bar, and that works.


In this implementation, when you use '_:

impl<'a, 'b, A: Foo<'a>, B: Foo<'b>> Foo<'_> for (A, B) { /* ... */ }

You're saying you can implement the trait for all lifetimes, and again the bound doesn't help you, because it's going in the wrong direction. [1]

Here's a version with the bound removed and with an explicit lifetime to make the warnings a little less opaque.

trait Foo<'a> {
    fn foo(bar: &'a Bar) -> Self;
}

impl<'a, 'b, 'x, A: Foo<'a>, B: Foo<'b>> Foo<'x> for (A, B) {
    fn foo(bar: &'x Bar) -> Self {
        (A::foo(bar), B::foo(bar))
    }
}

The warnings include

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src/lib.rs:14:10
   |
14 |         (A::foo(bar), B::foo(bar))
   |          ^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'x` as defined here...

We can add that constraint by having 'x: 'a, and then we'll need the same thing for 'b:

-impl<'a, 'b, 'x,          A: Foo<'a>, B: Foo<'b>> Foo<'x> for (A, B) {
+impl<'a, 'b, 'x: 'a + 'b, A: Foo<'a>, B: Foo<'b>> Foo<'x> for (A, B) {

And this compiles.

This is a long form answer of @mmlinford's solution; the only difference is that I dropped the Self bound on the method, which also meant I needed less bounds on the implementation. (I don't have enough context to know which is better.)


  1. Even then, eliding the lifetime in the method signature too leads to a meaning you didn't intend -- that the lifetime in the trait implementation and the lifetime in the method call are unrelated. If a trait has a lifetime, you usually want or need to specify it in the implementation. ↩ī¸Ž

3 Likes

Oh, I should have removed that Self bound. I think I did that first, then did the 'c bound, but didn't remove the Self. And nice explanation!

1 Like

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.