And this lecture we are going to talk about lifetimes, and it is important to note that every reference
has a lifetime.
Most of the time, lifetimes are implicit and inferred.
Some would say that this is rust most distinctive feature.
The main idea behind lifetimes is to prevent dangling references.
So let's set up a quick dangling reference.
So we're going to say let are and then we're going to open up another code block and say let x equals
five, and then we're going to assign a reference of X to R, and then we're going to come down here
and we want to try and print out R So if we run this, we see that it says the borrowed value does not
live long enough because X is dropped here.
And then here we try to reference ah which ah we tried to use ah which was a reference to x, but x
is no longer there.
So we have a dangling reference now and this is where lifetime annotations start to help us think through
this code and it helps the rest compiler find these dangling references for us ahead of time.
So as we dive into what lifetime annotations are, it is important to know that lifetime annotations
do not change how long any of the references live.
Lifetime annotations just describe the relationships of the lifetimes of multiple references to each
other without actually affecting their lifetime.
So to set up a lifetime, all we have to do is just use a tick.
So one, one single quote and then a you do not have to use an A, but that is what most people will
do.
So we can do a reference.
To an integer 32.
We can have a reference with an explicit lifetime setup of an I 32 and then we can have a reference
that is immutable with an explicit lifetime.
So let's look at a quick function and just try to make a little bit more sense about this.
So we're going to just call it an example function, assign here the lifetime.
And then inside we're going to have a parameter of X that's going to be a reference with an explicit
lifetime stated.
And then we're also going to return.
An explicit lifetime string slice.
So what we can see here is that the lifetime annotations.
Indicate that the reference X must both must live as long as the generic lifetime.
So we can see that all the function, the functions using the same lifetime, annotation, the parameters,
using it, and also the string slice.
So the function signatures going to tell us that for some lifetime of tick a the the function takes
parameter X which is a string slice and it must live.
As at least as long as the lifetime of take.
A So when we're returning a reference from a function, the lifetime parameter for the return type needs
to match the lifetime for one of the parameters.
So what the lifetime being returned here must match at least one parameter being passed in which in
this case we do satisfy.
So.
What exactly are the life times?
Why did they start?
And why are they in Rust?
Because it is a very unique feature.
So the history behind lifetimes has evolved as Rus has evolved.
So as we've done in past examples, we have had functions that have had references as parameters and
a reference returned at the end of the function.
But we did not have to specify a lifetime annotation.
So why were we able to do that?
Well, the reason to this is actually based on the history of Russ and earlier versions of Russ.
The Russ team noticed that programmers were entering the same lifetime annotations constantly, so they
were able to add these patterns into the compiler so the borrow checker could infer these lifetimes
in certain situations and would no longer need explicit annotations.
Knowing this, the rust language might continue to see these patterns added into the compiler, which
will allow programmers to less often add in these explicit lifetime annotations.
So far, the compiler uses three rules to infer these lifetimes when there aren't explicit annotations.
The first rule is that each parameter that is a reference gets its own lifetime parameter.
So if we have one parameter, we have one lifetime parameter to K, but if we have two parameters,
then we have two lifetime parameters.
So we can have tick A and tick B.
So just to make that clear, we can have to A for one parameter and then we can have tick B for second
parameter.
Then it continues on.
So if you have three, you have pixie and so on.
But the second rule is, if there is exactly one input lifetime parameter, that lifetime is assigned
to all output lifetime parameters aka the lifetime is assigned to the return value.
Lastly, the third rule is if there are multiple input lifetime parameters, but one of them is a reference
to a to the self or a immutable self.
And because in that case it is a method, the lifetime of self is assigned to all output life parameters.
So lifetime annotations aren't typically needed in methods because it's always going to return the lifetime
of self.
So let's take a look at an example of why we need lifetime annotations.
So we'll do another little one.
We'll just kind of modify the one above.
And we'll say Tick B and then we'll bring in Y.
Take be that as a string and then.
Expected a lifetime parameter so we can either return.
A lifetime of X.
Or we can return the lifetime of B.
But if we do that, notice how X is now squiggly here.
And it is saying the light, there's a lifetime mismatch.
So that's kind of so what we're looking at here is the first rule being applied because we have two
lifetime parameters.
But you can see that the second rule doesn't apply because there is more than one input lifetime because
we have ticked B and then since there is no cell, the third rule does not apply.
But we have to have a lifetime annotation on the return value because there was one given to the parameters.
And as we learned earlier, when returning a reference from a function, the lifetime parameter for
the return type needs to match the lifetime for one of the parameters.
So lifetime annotations are required in situations where the borrow checker doesn't know what to infer.
And it's important because we want the borrow checker to ensure that we have memory safety.