What does the first explicit lifetime specifier on an impl mean?
Asked Answered
K

3

37

There are three different lifetime specifiers on an impl:

impl<'a> Type<'a> {
    fn my_function(&self) -> &'a u32 {
        self.x
    }
}

Type<'a> states that there is a lifetime in this impl declaration. The one on the return type -> &'a u32 states that the variable that receives the return value should not die before...before what? Before the object of type Type dies?

What's the difference to this:

impl TextEditor {
    //Other methods omitted ...

    pub fn get_text<'a>(&'a self) -> &'a String {
        return &self.text;
    }
}

Here it says for the return type to not die before the lifetime of &'a self ends.

Does the last one declare only a lifetime to this one method and the other one declares a lifetime to every method (and associate function?) in the impl declaration?

Klinges answered 6/9, 2016 at 18:54 Comment(1)
Type<'a>might very well contain something that depends on 'a. Eg. on slice::Iter<'a, T>, 'a is the lifetime of the corresponding slice. as_slice(&self) -> &'a [T] can then retrieve it.Beta
P
34

'a is a lifetime parameter in both cases. That's a kind of generic parameter, so each use of Type or each use of get_text can pick a different "value" for that generic parameter. Actual lifetimes are never picked explicitly by the programmer, except when you use 'static.

The compiler will infer what 'a should be for each value of Type or each use of get_text.

impl<'a> introduces a new lifetime parameter for the whole impl block. It is then used in the type: impl<'a> Type<'a> { .. }

Exactly what 'a means depends on how it is used in the definition of Type. From your example, I guess that Type is this:

struct Type<'a> {
    x: &'a u32,
}

This definition reads: For every lifetime 'a, define Type to contain a reference x: &'a u32. So Type is generic and can store a reference with any lifetime.

impl<'a> Type<'a> { .. } reads: For every lifetime 'a, define methods for the type Type<'a>.

Since we now know the struct definition of Type, we know that the 'a parameter inside the impl block is always equal to the lifetime of the reference in Type's x field.

The one on the return type -> &'a u32 tells that the variable which is receiving the return value should not die before...before what? Before the object of type Type dies?

'a is the lifetime of the reference stored inside a value of Type<'a>, and it has no other relation to the Type value itself. The only rule is that 'a must outlive the Type value itself, because it is not allowed to store a reference past its end of life. So in fact, we can hold on to that &'a u32 until at least the point when the Type value dies, and maybe longer.


impl TextEditor {
    //Other methods omitted ...

    pub fn get_text<'a>(&'a self) -> &'a String {
        return &self.text;
    }
}

This is really common. &self is a reference to the self value — a borrow — and the method get_text is again a generic item. It has one generic parameter — a lifetime parameter.

It reads, for any lifetime 'a, borrow self as the reference &'a self (reference of that lifetime) and return a reference to String with the same lifetime.

The use of the same parameter on both the input &self and the output &String means that they are connected and Rust will treat self as borrowed as long as the returned reference to String is alive.

Again, the method get_text is generic, and the compiler will pick a "value" for 'a for each use of the method. It's a method that can return variously long borrows of a String, depending on how long you allow it to borrow self. Sometimes it picks a long lifetime so that you can hold onto the returned &String for a long time. Some uses of get_text will use a shorter lifetime, if you don't use the returned value at all.

In this case, since we see that &'a String is tied directly to a borrow &'a self of a TextEditor value, we know that we can only keep the String reference around at most as long as we can hold a borrow of the TextEditor value.

Pelag answered 6/9, 2016 at 20:27 Comment(1)
Thinking of <'a> notation as generics was an "aha" moment for me. Don't know why I didn't think of it before.Anuran
O
30

Paraphrasing the Rust code:

impl<'a>

"If you give me a lifetime..." (the compiler normally supplies this based on context when using the type)

         Type<'a> {

"...I'll describe how to implement Type<'a>". So Type probably contains references (which need a lifetime).

    fn my_function(&self) -> &'a u32 {

"...and given a reference to Type<'a>, you call my_function() to get a reference to a u32 with lifetime 'a." Note that the lifetime of the &self reference is not directly tied to 'a; it can be shorter (but usually not longer than 'a, since a type can't outlive contained references).

In the second case:

impl TextEditor {

"Here's how to implement a non-lifetime parametric type TextEditor..."

pub fn get_text<'a>

"Given a lifetime 'a which you can choose (it's an input parameter)..."

                   (&'a self)

"...and a reference to a TextEditor which lives for at least 'a.."

                             -> &'a String {

"...you can call the get_text method and receive a borrowed reference to a String which lives for the same time."

In more practical terms, this really means that the String is reborrowed directly from the TextEditor - as long as that String reference is alive, the &self borrow is considered to still be active and you won't be able to take any &mut references.

Omnipresent answered 6/9, 2016 at 20:40 Comment(1)
thanks, this was incredibly helpful! I find I understand most of Rust's concepts in isolation but often struggle when they're put together in practice. translating terse syntax into explanatory prose is a great way to understand what's going on. I wish read-along code examples were more of a thing :)Polyhymnia
G
2

The essence of the question has to do with the scoping of 'a:

situation 'a scoping
impl<'a> { fn } 'a may be used for any function inside
impl { fn<'a> } 'a is specific to each function

In both cases, 'a is a generic lifetime parameter. For either function, there is no difference in the semantics of 'a.

This table illustrates the scoping difference, but it doesn't highlight the practical benefits of specifying lifetimes at the impl level. In situations where you have multiple methods that need to work with the same lifetime constraints, 'hoisting' the lifetime syntax to the impl level establishes a consistent constraint across all methods in that block.

Glaciate answered 7/9, 2023 at 16:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.