English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Rust Lifetimes

The Rust lifetime mechanism is an important resource management mechanism that is on par with the ownership mechanism.

The main reason for introducing this concept is to deal with the problem of resource management in complex type systems.

References are an indispensable mechanism when dealing with complex types, after all, the data of complex types cannot be easily copied and calculated by processors.

But references often lead to extremely complex resource management issues, let's first get to know what dangling references are:

{
    let r;
    {
        let x = 5;
        r = &x;
    }
    println!("r: {}", r);
}

This code will not pass the Rust compiler because the value r refers to has been released before use.

The green range 'a in the figure above represents the lifetime of r, and the blue range 'b represents the lifetime of x. It is obvious that 'b is much smaller than 'a, and the reference must be within the lifetime of the value to be valid.

All along, we have used String in the structure instead of &str, and we will explain the reason with an example:

fn longer(s1: &'str, s2: &'str) -> &str {
    if s2.len() > s1.len() {
        s2
    } else {
        s1
    }
}

The longer function takes s1 and s2 The longer of the two string slices returns its reference value. But this code will not pass the compilation because the returned reference value may return an expired reference:

fn main() {
    let r;
    {
        let s1 = "rust";
        let s2 = "ecmascript";
        r = longer(s1, s2);
    }
    println!("{} is longer", r);
}

Although comparison has been made in this program, when r is used, the source value s1 and s2 have become invalid. Of course, we can move the use of r to s1 and s2 prevent such errors within the scope of the lifetime, but for functions, it cannot know what is outside of itself. To ensure that the values it passes out are normal, it must eliminate all risks with the ownership principle, so the longer function cannot be compiled.

Lifetime annotations

Lifetime annotations are a way to describe the lifetime of references.

Although this cannot change the lifetime of the reference, it can declare the lifetimes of two references to be consistent in appropriate places.

Lifetime annotations start with a single quote followed by a lowercase word:

&i32        // Conventional reference
&'a i32     // Reference with lifetime annotation
&'a mut i32 // Reference with lifetime annotation in a mutable type

Let's transform the longer function with lifetime annotations:

fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s2.len() > s1.len() {
        s2
    } else {
        s1
    }
}

We need to use generic declaration to standardize the name of the lifetime, so that the lifetime of the return value of the function will be consistent with the lifetimes of the two parameters. Therefore, we can write like this when calling:

fn main() {
    let r;
    {
        let s1 = "rust";
        let s2 = "ecmascript";
        r = longer(s1, s2);
        println!("{} is longer", r);
    }
}

The running result of the combined two segments above:

ecmascript is longer

Note:Don't forget the principle of automatic type judgment.

String slicing reference is used in the structure.

This is the question left over before, and here is the answer:

fn main() {
    struct Str<'a> {
        content: &'a str
    }
    let s = Str {
        content: "string_slice"
    };
    println!("s.content = {}", s.content);
}

Running Result:

s.content = string_slice

If there is a method definition for the struct Str:

impl<'a> Str<'a> {
    fn get_content(&self) -> &str {
        self.content
    }
}

The return value does not have a lifetime annotation, but it is not necessary. This is a historical issue. Early Rust did not support automatic lifetime judgment, and all lifetimes had to be declared strictly, but the mainstream stable version of Rust already supports this feature.

Static Lifetime

There is a special one for lifetime annotations: 'static. All exact data types represented by strings constants enclosed in double quotes are &'static str, and 'static represents the lifetime from the start of the program to the end of the program.

Collaboration of Generics, Traits, and Lifetimes

use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
    where T: Display
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

This program is from the Rust Bible, a program that uses generic, trait, and lifetime mechanisms at the same time. It is not mandatory, you can experience it, after all, it will be needed sooner or later!