Lifetimes

The third piece in Rust's compile time memory safety puzzle is the idea of lifetimes and the related syntactic annotation for specifying lifetimes in code. In this section, we'll explain lifetimes by stripping them down to the basics.

When we declare a variable by initializing it with a value, the variable has a certain lifetime, beyond which it is invalid to use it. In general programming parlance, the lifetime of a variable is the region in code in which the variable points to a valid memory. If you have ever programmed in C, you should be acutely aware of the case with lifetimes of variables: every time you allocate a variable with malloc, it should have an owner, and that owner should reliably decide when that variable's life ends and when the memory gets freed. But the worst thing is, it's not enforced by the C compiler; rather, it's the programmer's responsibility.

For data allocated on the stack, we can easily reason by looking at the code and figure out whether a variable is alive or not. For heap allocated values, though, this isn't clear. Lifetimes in Rust is a concrete construct and not a conceptual idea as in C. They do the same kind of analysis that a programmer does manually, that is, by examining the scope of value and any variable that references it.

When talking about lifetimes in Rust, you only need to deal with them when you have a reference. All references in Rust have an implicit lifetime information attached to them. A lifetime defines how long the reference lives in relation to the original owner of the value and also the extent of the scope of the reference. Most of the time, it is implicit and the compiler figures out the lifetime of the variables by looking at the code. But in some cases, the compiler cannot and then it needs our help, or better said, it asks you to specify your intent.

So far, we have been dealing with references and borrowing quite easily in the previous code examples, but let see what happens when we try to compile the following code:

// lifetime_basics.rs

struct SomeRef<T> {
part: &T
}

fn main() {
let a = SomeRef { part: &43 };
}

This code is very simple. We have a SomeRef struct, which stores a reference to a generic type, T. In main, we create an instance of the struct, initializing the part field with a reference to an i32, that is, &43.

It gives the following error upon compilation:

In this case, the compiler asks us to put in something called a lifetime parameter. A lifetime parameter is very similar to a generic type parameter. Where a generic type T denotes any type, lifetime parameters denote the region or the span where the reference is valid to be used. It's just there for the compiler to fill in with the actual region information later when the code is analyzed by the borrow checker.

A lifetime is purely a compile time construct that helps the compiler to figure out the extent to which a reference can be used within a scope, and ensures that it follows the borrowing rules. It can keep track of things like the origin of references and whether they outlive the borrowed value. Lifetimes in Rust ensure that a reference can't outlive the value it points to. Lifetimes are not something that you as a developer will use, but it's for the compiler to use and reason about validity of references.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset