YouTip LogoYouTip

Rust Lifetime

Rust Lifetime |

-- Learning not just technology, but dreams!

Rust Tutorial

Rust Tutorial Rust Introduction Rust Environment Setup Cargo Tutorial Rust Output to Command Line Rust Basic Syntax Rust Operators Rust Data Types Rust Comments Rust Functions Rust Conditional Statements Rust Loops Rust Iterators Rust Closures Rust Ownership Rust Slice Type Rust Structs Rust Enums Rust Project Management Rust Error Handling Rust Generics and Traits Rust Lifetime Rust File and IO Rust Collections and Strings Rust Object-Oriented Rust Concurrency Rust Macros Rust Smart Pointers Rust Asynchronous Programming async/await

Rust Generics and Traits Rust File and IO

Deep Dive

  • Programming
  • Computer Science
  • Web Design & Development
  • Software
  • Development Tools
  • Web Service
  • Programming Languages
  • Scripting
  • Scripting Languages
  • Web Services

Rust Lifetime

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

The reason for introducing this concept is mainly to address resource management issues in complex type systems.

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

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

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 been freed before it is used.

Image 2

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

We have been using String instead of &str in structs. Let's explain the reason with an example:

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, returns a reference to the longer one. However, this code will not compile because the return value reference might return an expired 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 is made, when r is used, the source values s1 and s2 are both invalid. Of course, we can move the use of r within the lifetime scope of s1 and s2 to prevent this error from occurring. But for the function, it doesn't know the situation outside itself. To ensure that the value it passes out is valid, it must follow the ownership principle to eliminate all dangers. Therefore, the longer function cannot pass compilation.

Lifetime Annotations

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

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

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

&i32        // a reference
&'a i32     // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

Let's use lifetime annotations to modify the longer function:

Example

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

We need to use a generic declaration to specify the name of the lifetime. Then the lifetime of the function's return value will be consistent with the lifetimes of the two parameters. So when calling, we can write it like this:

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, now answered:

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 we define methods for the struct Str:

Example

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

Here, the return value doesn't have a lifetime annotation, but adding one wouldn't hurt. This is a historical issue. Early Rust did not support automatic lifetime inference, and all lifetimes had to be strictly declared. However, mainstream stable versions of Rust now 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 represented 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. It is a program that uses generics, traits, and lifetime mechanisms simultaneously. It's not mandatory, but you can try it out, as you'll eventually need it!

← Os ListdirOs Lchmod β†’