Rust Lifetimes

The Rust lifetime mechanism is a resource management mechanism as important as the ownership mechanism.

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

References are an essential mechanism when dealing with complex types, after all, data of complex types cannot be easily copied and computed by the processor.

But references often lead to extremely complex resource management problems. First, let's understand dangling references:

Example

{
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}

This code will not pass the Rust compiler because the value referenced by r has already been dropped before it is used.

In the above diagram, the green range 'a represents r's lifetime, and the blue range 'b represents x's lifetime. Obviously, 'b is much shorter than 'a; a reference must be within the lifetime of the value to be valid.

We have always used String rather than &str in structs. Let's use an example to explain why:

Example

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

The longer function takes two string slices, s1 and s2, and returns the reference of the longer one. However, this code will not compile because the returned reference may be a dangling reference:

Example

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

In this program, although a comparison has been made, by the time r is used, the source values s1 and s2 have already become invalid. Of course, we could move the use of r into the lifetime scope of s1 and s2 to prevent this error, but for the function, it cannot know what is happening outside itself. In order to ensure that the value it passes out is valid, it must follow the ownership principle to eliminate all dangers, so the longer function cannot compile.

Lifetime Annotation

Lifetime annotation is a way to describe the lifetime of references.

Although this cannot change the lifetime of references, it can declare that the lifetimes of two references are consistent in appropriate places.

A lifetime annotation starts with an apostrophe and is followed by a lowercase word:

&i32        // 常规引用
&'a i32     // 含有生命周期注释的引用
&'a mut i32 // 可变型含有生命周期注释的引用

Let's refactor the longer function using lifetime annotations:

Example

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

We need to use generic declarations to specify the lifetime names; then the lifetime of the function's return value will be consistent with the lifetimes of the two parameters, so when calling it we can write:

Example

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

The combined output of the above two programs:

ecmascript is longer

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

Using String Slice References in Structs

This is the question left earlier; here is the answer:

Example

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

Output:

s.content = string_slice

If there is a method definition for the struct Str:

Example

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

The return value here does not have a lifetime annotation, but adding one doesn't hurt. This is a historical issue: early Rust did not support automatic lifetime inference, and all lifetimes had to be strictly declared, but mainstream stable versions of Rust already support this feature.

Static Lifetime

There is a special lifetime annotation: 'static. The exact data type represented by all string constants enclosed in double quotes is &'static str; the lifetime denoted by 'static lasts from the start of program execution to the end of program execution.

Generics, Traits, and Lifetimes Working Together

Example

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 comes from the Rust Bible (Rust Book). It is a program that uses generics, traits, and lifetime mechanisms at the same time. It is not mandatory; you can try it out, after all, you will need it sooner or later!

Other Extensions