Lifetimes are pretty much what makes Rust Rust.
Easy concurrency, straightforward memory allocation, and overall data safety would not be possible without explicit lifetimes.
But they are also tricky, and this is aimed at helping people understand the concepts and syntax.
Rust is a unique language in that it deallocates memory on the heap without requiring the writer to call
free, while at the same time having no need for a garbage collector. Rust knows when its okay to use a reference by keeping track of its lifetime.
Each time a reference is returned by or passed into a function, Rust checks at compile time to make sure it fulfills the lifetime requirement specified in the type signature.
So every reference in Rust (i.e.pointer) has a lifetime. A lifetime is part of the type signature for any reference. Sometimes they can be elided and the compiler can infer them. Nonetheless, you cannot program Rust without knowing how to specify lifetimes.
Lifetimes fulfill two roles for Rust:
To know when its safe to dereference a pointer
To allow data to be shared safely
Now, you are not in charge of defining lifetimes.
Sometimes, however, you will have to give names to existing lifetimes in order to change the default behavior of the compiler.
Lifetimes are always named with the same syntax. They look like
<'c>, and they are generally one letter prefaced by an apostrophe.
When you use one, though, there is more syntax involved. Depending on the context they can look like one of:
Box<something + 'a> or
Note well: You will only ever write a lifetime within a type declaration.
So if you dont define lifetimes, what does?
This is a biggie. A function can define a lifetime that can be used in its type declarations.
This makes sense: If you want to take a reference to something on the function stack, you have to be prepared for it to disappear when the function is over. And if not, you need to make sure it has an appropriate lifetime.
In Go, you can do this:
However, you cannot do that in Rust:
The reason is that
&b does not live long enough to be dereferenced outside of the function that made it.
Why is this so important? Because this is what can happen in C:
In C this is Undefined Behavior. When I run this, it just prints 3.
Thats right, the data can just change under your nose. I was honestly scared when I ran that. But Rust prevents crazy things like that from taking place!
So just to explicitly point out the syntax:
example_function<'a> is saying for any lifetime called
'a. You can then go on to use this lifetime in the remainder of the type definition.
&'a i32 says This is a reference to an integer that has lifetime
'a, which means it has to last as long as any lifetime. However,
b happens to only have a lifetime that lasts as long as the functions scope, so Rust complains.
(Now at this point you might be asking how you actually would return a reference to
3 in Rust thats a more complicated question and the answer is to put it on the heap. Look up the
Box type to learn more.)
And now on to bigger fish.
Structs also define lifetimes.
Think about it like this:
If a struct includes a reference to something, then that reference damn sure better last as long as the struct.
Heres how you can make sure of that:
This gives the following error:
Rust is mad. In the struct definition, you havent told it how long the reference to the
i32 is allowed to stay around. And yet, in order for your code to be safe, it has to stick around for at least as long as the struct.
Youll notice that not much here has changed. But now Rust has pronounced your code safe Hurray!
Here are the changes:
This is making a parameter for the struct in Rust Its lifetime might have to depend on the lifetimes of its fields, so now you are able to say how and which ones. (Note: you can do things like
Sheep<'c, 'd> if you need more than one lifetime.)
age: &'c i32 instead of
Now this says that the integer has to live for as long as the struct.
This is insanely impressive. With these small additions, Rust will now tell you if there is any chance of you having invalid data, even across threads. AND all of this happens at compile time without affecting the efficiency of your code.
There is one other place where a lifetime can be defined Implementations.
Its very similar to structs. Each implementation is an implementation of a certain trait for a struct. So if the struct requires an explicit lifetime, you need to have one to give it.
This fails with the following:
Whats the problem?
Sheep takes a lifetime parameter now, so one must be supplied:
impl<'c> names a new lifetime and
Sheep<'c> says that all
selfs in this implementation have at least the lifetime
This is a lot like an extenuation of the lifetimes with structs, but there is strange syntax with the
impl<'c> Sheep<'c> so I wanted to point it out.
And that is all I know about lifetimes in Rust!
If youre interested, here is a concise reference for the syntax used with lifetimes.