Why is here an issue of lifetime?

I don't understand what the error message means here and why would there be a problem in this code.
Code:

trait A {
    fn f(&mut self) -> &mut ();
}

trait B<T>
where
    T: A,
{
    fn g(&mut self) -> &mut T;
    fn h(&mut self) -> &mut (){
        let t = self.g();
        t.f()
    }
}

fn main() {}

Error message :

error[E0311]: the parameter type `T` may not live long enough
   --> src/zips.rs:148:17
    |
148 |         let t = self.g();
    |                 ^^^^^^^^
    |
note: the parameter type `T` must be valid for the anonymous lifetime defined here...
   --> src/zips.rs:147:10
    |
147 |     fn h(&mut self) -> &mut (){
    |          ^^^^^^^^^
note: ...so that the type `T` will meet its required lifetime bounds
   --> src/zips.rs:148:17
    |
148 |         let t = self.g();
    |                 ^^^^^^^^
help: consider adding an explicit lifetime bound...
    |
144 |     T: A + 'a,
    |          ++++

A function that takes a &'m mut T has an implicit T: 'm bound which the caller of the function must satisfy (or an error results). And in your trait,

    // This method has implicit `Self: 'g` and `T: 'g` bounds
    fn g<'g>(&'g mut self) -> &'g mut T;
    // This method has an implicit `Self: 'h` bound but no bound for `T`
    fn h<'h>(&'h mut self) -> &'h mut () {
        // So it can't "prove" that `T: 'h`, because it's possible to
        // call `h` with some lifetime `'h` that `T` can't meet
        // (even if convoluted to construct)
        let t = self.g();
        t.f()
    }

Adding an explicit bound fixes the error.

5 Likes

Well that’s a fun error message. Took me a minute; now, after me figuring out the solution, @quinedot already posted an explanation, too. Anyways, I wanted to acknowledge that there’s also at least one clear issue with the error message itself, namely the suggestion

help: consider adding an explicit lifetime bound...
   |
7  |     T: A + 'a,
   |          ++++

which suggests adding a lifetime bound

  • for a lifetime that doesn’t have a name, without clearly indicating that 'a is not the correct name yet (and perhaps suggesting how to introduce a name)
  • at a place (top level of the trait) where the lifetime in question (elided lifetime argument of the method) is not even in scope
3 Likes

A clearer msg occurs when annotating the type: Rust Playground

    fn h<'h>(&'h mut self) -> &'h mut () 
    {
        let t: &'h mut T = self.g();
        t.f()
    }

error[E0309]: the parameter type `T` may not live long enough
  --> src/main.rs:18:16
   |
18 |         let t: &'h mut T = self.g();
   |                ^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
8  |     T: A + 'h,
   |          ++++

error[E0309]: the parameter type `T` may not live long enough
  --> src/main.rs:18:28
   |
18 |         let t: &'h mut T = self.g();
   |                            ^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
8  |     T: A + 'h,
   |          ++++

error[E0309]: the parameter type `T` may not live long enough
  --> src/main.rs:19:9
   |
19 |         t.f()
   |         ^^^^^ ...so that the type `T` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
8  |     T: A + 'h,
   |          ++++

Thank you for the explanation. Let me digest.

Since T can potentially be a reference type, it has a lifetime 't. For the function g, we implicitly require that T as a reference type must outlive the elided output lifetime ('g). And since in the function h we call g, the lifetime requirement is not guaranteed to be met. By adding this as assumption, we allow the body then to call g and require explicitly the proof from the caller of h.

From a beginner's perspective, the thing that bugged me was forgetting T can potentially be a reference type AND this results in the 't: 'g. I had always been thinking about the lifetime only of the reference explicitly shown in my code with &.

You have the gist of it.

To nitpick, types other than references can have lifetimes,[1] so "reference type" or "be a reference" is somewhat inaccurate. The phrase "T [...] has a lifetime" (my emphasis) also feels off to me, though I can't exactly pintpoint why.[2] I'd say, every type T can meet or fail to meet a T: 'lifetime bound, or in this case perhaps "T may not be valid for the reference lifetime". But those are just quibbles.

I suggest reading these common lifetime misconceptions. Just skip or skim the sections that don't click, you can always return to it later.


  1. even though almost always the root source of a lifetime is a reference ↩︎

  2. Perhaps because for example, the function pointer fn(&'a str) -> &'a str does not "have" anything in my mind, but it can't meet a 'static bound. ↩︎

2 Likes