There is a time in your life when you have to understand lifetimes

Hi,

Sorry for the stupid title of this post :blush:

I am afraid that I didn't clear understand how the lifetimes work, despite I have read Rust book and Rust by example, I still don't get them!

For example take this simple case:

fn foo<'a>(x: &'a T) -> &'a U { ... }

I would read it as: foo is a function that takes an argument named x that is a reference to type T and returns a reference to a value of type U, x should have the same lifetime of the returned value

Is that right?

So what about this other common example:

struct Foo<'a> {
   x: &'a i32,
}

I would read it as: Foo is a struct that contains a field named x that is a reference to i32 and it should have the same lifetime of the struct.

Is that right? I guess it is not right... Since something like the following would not make sense:

struct Foo<'a, 'b> {
    x: &'a i32,
    y: &'b i32,
}

So could you please me explain better how they work or provide me a link to a tutorial more clear than what I have already read.
I am pretty familiar with concept of "scope", so I think my understanding problem is not related to that.

Thank you.

3 Likes

You are almost correct. The tiny, but fundamental, difference is that the lifetimes are not necessary equal in length

Using your examples, the lifetime of Foo<'a> is at most as long as 'a, but 'a may be longer. The reference can still exist after the destruction of Foo. That makes Foo<'a, 'b> more sane. It says that its lifetime can not be longer than the shortest of 'a and 'b.

The last example may cause conflicts and force you do do something like 'a: 'b to say that 'a is at least as long as 'b. You can also use 'a for both references, saying that they have to exist simultaneously, but not for how long. One can go out of scope before the other, but Foo has to go first.

I hope this cleared it up a little.

3 Likes

This exactly equal to a function written like this:

fn foo(x: &T) -> &U { ... }

Just to have lifetime elision clear.

Don't forget the lifetime's relation to borrowing. The relation formed between the input x and the return value is a borrowing relation. Rustc will know and mark that the return value from foo was borrowed from x.

Thank you @ogeon, your answer it's pretty clear!

Just to be check that I got it right:

So when I find lifetime declarations in a struct they are used to specify that the instances of the struct cannot outlive their reference fields.

While it's not still fully clear how it works with the function.

When I find lifetime declarations in functions (thank to @bluss) they are used to put in relation input parameters lifetime and output argument lifetime.
What is not still clear to me is which is the relationship: output argument cannot outlive input or viceversa? Or maybe something else?

So anyway something like the following has no sense/is useless:

fn foo<'a> (x: &'a i32) {...}
fn bar<'a> () -> &'a i32 {...}

Am I right?

Thank you.

Yes, exactly.

You could interpret it as if the output of your function is forbidden to outlive the input. It is essentially treated as if it's borrowed from x (see the comment from @bluss).

This makes perfect sense. It's the same as fn foo(x: &i32) {...} and the lifetime of x is at most required to be valid for foo. (see the comment from @bluss again)

This makes less sense. Where is the number borrowed from? There is no input that 'a can be tied to.

1 Like

Thank you so much @ogeon, now I think lifetimes are more clear to me!
Also thank to @mdinger that has quote my question in this thread and make me discovered other useful questions-answers about lifetime.