[Rust Basics] Rust Life Cycle

preface

Rust is a strongly typed, statically analyzed systems programming language that is memory safe and concurrency safe. To achieve these safety features, Rust introduces the concept of lifetimes. This blog will introduce in detail the definition, use and related concepts of the Rust life cycle, as well as how to correctly handle the life cycle of references.

Definition of life cycle

A lifetime describes the validity period of a reference, that is, the time frame during which the data it refers to can be safely accessed by the reference. In Rust, lifetimes are a compile-time static checking mechanism used to ensure the safety of references. Lifecycle annotations are a way to enable the compiler to deduce the valid scope of references by marking the relationship between references.

Lifecycle annotations

In Rust, we use lifetime annotations (lifetimes annotations) to mark the relationship between references, thereby specifying the valid scope of references. Lifecycle annotations start with an apostrophe 'followed by an identifier. Usually, we use a single letter to represent a lifetime.

Here's an example that demonstrates how to use lifecycle annotations:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    
    
    if x.len() > y.len() {
    
    
        x
    } else {
    
    
        y
    }
}

fn main() {
    
    
    let s1 = "hello";
    let s2 = "world";
    let result = longest(s1, s2);
    println!("The longest string is: {}", result);
}

In the above example, we defined a longestfunction named that takes two string references xand and returns a string reference ywith lifetime . 'aWith lifetime annotations 'a, we tell the compiler that the returned reference should have the same lifetime as xand y, i.e. they should be valid in the same scope.

Lifecycle omission rules

In Rust, there are some cases where lifetime annotations can be omitted because the compiler infers them according to certain rules.

  1. When each referenced parameter has a different lifetime, the compiler will automatically infer the lifetime based on the order of the parameters.
  2. If there is only one input lifetime parameter, then that lifetime will be assigned to all output lifetime parameters.
  3. If a method has multiple input lifetime parameters, but one of the parameters is &selfor &mut self, then selfthe lifetime of will be assigned to all output lifetime parameters.

Here is an example demonstrating the application of the lifetime omission rule:

fn longest(x: &str, y: &str) -> &str {
    
    
    if x.len() > y.len() {
    
    
        x
    } else {
    
    
        y
    }
}

fn main() {
    
    
    let s1 = "hello";
    let s2 = "world";
    let result = longest(s1, s2);
    println!("The longest string is: {}", result);
}

In the above example, we omitted the lifetime annotation, but the compiler inferred the lifetime based on the order of the parameters. Since s1and s2are different references, the compiler automatically infers their lifetimes and assigns the same lifetime to the returned reference.

lifetime limit

In some cases, we may need to explicitly specify lifetime relationships to satisfy certain constraints. In Rust, lifetime constraints are implemented through lifetime parameters and trait constraints.

Here's an example that demonstrates how to use lifetime limits:

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
    }
}

fn main() {
    
    
    let s1 = "hello";
    let s2 = "world";
    let result = longest_with_an_announcement(s1, s2, "Calculating longest string...");
    println!("The longest string is: {}", result);
}

In the above example, we defined a longest_with_an_announcementfunction named that takes two string references xand y, and a generic parameter that implements Displaytrait ann. Through life cycle annotations 'a, we limit the life cycle of x, yand return values, making them valid in the same scope. Using trait constraints T: Display, we require that annthe parameter must implement Displaythe trait.

More complexities of the life cycle

Sometimes, the life cycle relationship between references is complicated, and it is necessary to use life cycle parameters and life cycle omission rules to specify the correct life cycle. These complex situations include nested calls of functions, life cycles of structures and enumerations, and so on.

In these cases, proper understanding and use of lifecycles will ensure correct and safe code. In actual development, you can debug and solve lifecycle-related problems by writing test cases and using Rust's compiler error messages.

Summarize

This blog introduces the definition, use and related concepts of the Rust life cycle in detail. Through life cycle annotations and life cycle omission rules, we can specify the valid range of references and ensure the security and correctness of the code.

I hope this blog helps you understand and apply the Rust life cycle. Thanks for reading!

おすすめ

転載: blog.csdn.net/qq_21484461/article/details/131629425