Lifetime parameter about Self and specified type

Hello, everyone!
I have a question about why I need to add lifetime parameter on parameter when I change the returned type just like below code:

trait Messenger {
    fn send(&self, message: &str);
}

pub struct LimitTracker<'a, T> {
    messenger: &'a T,
    value: i32,
    max: i32,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    // will change here
    fn new(messenger: &T, max: i32) -> LimitTracker<T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }
}

but when I change the returned type from LimitTracker<T> to Self, the compiler gives an error and requires adding a lifetime parameter on messenger: &T:

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    // change here
    fn new(messenger: &T, max: i32) -> Self {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }
}

the error message is:

$ cargo check
    Checking tests v0.1.0 (/Users/zhengxk/development/rust/projects/tests)
error[E0621]: explicit lifetime required in the type of `messenger`
  --> src/lib.rs:16:9
   |
15 |       fn new(messenger: &T, max: i32) -> Self {
   |                         -- help: add explicit lifetime `'a` to the type of `messenger`: `&'a T`
16 | /         LimitTracker {
17 | |             messenger,
18 | |             value: 0,
19 | |             max,
20 | |         }
   | |_________^ lifetime `'a` required

For more information about this error, try `rustc --explain E0621`.
error: could not compile `tests` due to previous error

I just know Self means the type LimitTracker, but don't know why I should add the lifetime parameter on messenger: &T after I changed the returned type.

When you elide the lifetime altogether in the return type, it's as if you wrote LimitTracker<'_, T> -- the lifetime is inferred (in this case, to be the only lifetime from the input parameters, which is anonymous).

But when you use Self, it's a direct alias -- LimitTracker<'a, T>. So you need 'a on the input parameter, or they won't match. (You've opted out of elision.)

Incidentally, writing out LimitTracker<'_, T> makes it more evident that the return value is a borrow.

1 Like

Come to think of it, which you want is sort of vague here, with the associated function. Eliding is more flexible but might not be Self, if that's important. [1] (Sorry for the brevity, on mobile.)


  1. Types that differ only in lifetime are still different types. ↩ī¸Ž

1 Like

According to your answer, I found that I have a lacking of elision rules and haven't really understand how the lifetime is inferred. Thanks a lot!

Here's a useful section on the topic in the reference:

fn new1(buf: &mut [u8]) -> Thing<'_>;                 // elided - preferred
fn new2(buf: &mut [u8]) -> Thing;                     // elided
fn new3<'a>(buf: &'a mut [u8]) -> Thing<'a>;          // expanded

One thing I don't think it mentions is that elision can't change the Self alias (as that has no lifetime parameters, even when it currently aliases a type that does happen to have them).


I'd probably stick with elision in this case. The only case Self won't work for is calling Self::new(...) in another method and trying to get a longer lifetime out, but on the off chance you run into that, it might be confusing. (The compiler will infer the same lifetimes when you call LimitTracker::new(...) with either approach.)

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.