Implement trait with multiple generic lifetime for exclusive reference?

I'm on my way to learning new lifetime concept and problems,

This question is kinda similar to my previous question about lifetime (generic tuples with multiple lifetimes), Where @quinedot explained the problem very nicely!

However today, I encountered a new problem,

Without generic, This code compiled:

struct Bar;
trait Foo {
    fn foo(bar: &mut Bar) -> Self;
}
impl<A: Foo, B: Foo> Foo for (A, B) {
    fn foo(bar: &mut Bar) -> Self {
        (A::foo(bar), B::foo(bar))
    }
}

Unfortunately This doesn't, (Playground)

// Error[E0499]: cannot borrow `*bar` as mutable more than once at a time

trait Foo<'a> {
    fn foo(bar: &'a mut Bar) -> Self;
}
impl<'a, A, B> Foo<'a> for (A, B)
//   ^^  lifetime 'a defined here
where
    A: Foo<'a>,
    B: Foo<'a>,
{
    fn foo(bar: &'a mut Bar) -> Self {
        (A::foo(bar), B::foo(bar))
//       -----------         ^^^ second mutable borrow occurs here
//       |      |
//       |      first mutable borrow occurs here
//       argument requires that `*bar` is borrowed for 'a
    }
}

I'll assume you're after explanations and understanding, versus something practical.

&'lifetime mut T is covariant over the the lifetime, while parameters in traits such as Foo<'lifetime> are invariant over the lifetime. See the reference and the nomicon for more details, but the executive summary is that

  • &'long mut T can coerce into a &'short mut T if need be, but
  • A: Foo<'long> does not imply A: Foo<'short> (or A: Foo<'longer>)

So with that in mind, let's look at your trait-with-generics.

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

Each implementation of this trait let's me call foo with a borrow of lifetime 'a -- and no other lifetime, only 'a will do. If my implementation is generic, it might cover all possible lifetimes:

struct S;
impl<'some_lifetime> Foo<'some_lifetime> for S {
    fn foo(bar: &'some_lifetime mut Bar) -> Self {
        S
    }
}

But the implementation is distinct for every lifetime. That is, you can only call foo(&'x mut Bar) if Foo<'x> specifically is implemented.


OK, but how about using it?

impl<'a, A, B> Foo<'a> for (A, B)
where
    A: Foo<'a>,
    B: Foo<'a>,

Within this implementation, you can assume your bounds have been met -- but nothing more. And your bounds say that you only have one specific lifetime available -- 'a -- and that A and B implement Foo<'a> for that one specific lifetime. You cannot assume they implement Foo<'shorter_than_a> (or 'longer) due to the invariance of the trait parameter.

So then in the implementation body:

    fn foo(bar: &'a mut Bar) -> Self {
        (A::foo(bar), B::foo(bar))

The only foos you can call are

  • <A as Foo<'a>>::foo(&'a mut Bar)
  • <B as Foo<'a>>::foo(&'a mut Bar)

Because that's all that is permitted by your bounds. You only know Foo<'a> is implemented, so you can only call foo with a lifetime of 'a, as per the trait definition. These two borrows would clearly overlap -- they're exactly the same -- and that is not permitted (aliasing a &mut is instant UB).


Why does the version without generics work?

trait Foo {
    fn foo(bar: &mut Bar) -> Self;
    // Same as:
    // fn foo<'any>(bar: &'any mut Bar) -> Self;
}

The lifetime in foo is no longer tied to a trait parameter. In order to implement this trait, you have to be able to write a foo that takes a &mut Bar of any lifetime, not just one specific lifetime. So when it comes to the tuple implementation:

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

We can make it a lot more verbose like so:

    fn foo<'x>(bar: &'x mut Bar) -> Self {
        let a = <A as Foo>::foo(bar); // Reborrow 1
        let b = <B as Foo>::foo(bar); // Reborrow 2
        (a, b)
    }

We get a &'x mut Bar, which is covariant over 'x. So when we call A::foo, we can reborrow bar as a &'very_short mut Bar. The reborrow ends as soon as A::foo returns. Something similar happens with B::foo [1]. Due to these short reborrows, there is no overlap. The calls themselves are valid because to implement Foo, your foo function must accept any lifetime; we don't have to call them with 'x specifically. We call each with something shorter, as short as the function call itself.

The &'a mut Bar was covariant in the generic example too, but that didn't help us because we had to call the foo functions with a lifetime 'a. That's all the bounds allowed; we couldn't use anything shorter.


I assumed you knew about reborrows there. I wish I had a good reference for reborrows, but the official documentation on them is poor. Basically when you reborrow something:

let reborrow: &mut Bar = &mut *bar;

It's not an overlap because this is reusing the original borrow, sort of like you can do:

let data_structure: &mut Data = todo!();
let field: &mut String = &mut data_structure.field;

Because the lifetime is covariant, the reborrow can have a shorter lifetime. Once your reborrow expires, you can use the original again.

Rust inserts reborrows automatically in some places, and the above example is one of them.


So now that we see how the original worked, can we "fix" the generic version?

Yes, easily! Remove the lifetime from the trait! :wink: [2]

Okay, okay, let's try something else. The problem was that we could only call A::foo and B::foo with the same lifetime. What if we could call them with any lifetime, similar to how it worked without generics?

 impl<'a, A, B> Foo<'a> for (A, B)
 where
-    A: Foo<'a>,
-    B: Foo<'a>,
+    A: for<'any> Foo<'any>,
+    B: for<'any> Foo<'any>,

That works. These new bounds say that A and B must implement Foo<'_> for all possible lifetimes, and that means that we can call A::foo and B::foo with any lifetime, just like the non-generic case. (This shows more directly that the &'a mut Bar is still covariant -- we can reborrow shorter lifetimes from it.)

Those bounds are called higher-ranked trait bounds (HRTBs).


  1. though technically it doesn't need to, as this is the last place we use bar ↩︎

  2. More seriously, lifetimes on traits are a yellow flag. Sometimes they're useful or necessary but often they are problematic and indicate a disconnect elsewhere. There's no need for the lifetime in this example. ↩︎

3 Likes

Thank you very much!

@quinedot, What should be the title of this question ?

In the future, if someone encounters the same problem, so that they can easily find this useful information.

Something about implementing traits with lifetimes? If someone had a use case very similar to this one, I'd just strongly suggest removing the lifetime from the trait. If you can't exhibit a need for a lifetime parameter, you probably don't want one.

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.