Understanding the life cycle in rust

Time:2021-12-30

Original link:Understanding lifetimes in Rust
Understanding the life cycle in rust

Today is a good day. You want to try rust programming again. The last attempt was very smooth, except for a littleBorrowing checkBut in the process of solving the problem, you understand its working mechanism, and everything is worth it!

Borrowing check can’t stop your rust grand plan. After conquering it, you feel that you can write rust code quickly. Then you enter the compilation instruction again and start compiling. However:

error[E0597]: `x` does not live long enough

Again? You take a deep breath, relax and look at this error message. “Not long enough(does not live long enough)”, what the hell does that mean?

Life cycle introduction

The rust compiler uses a lifecycle to record whether a reference still exists. Reference checking is also a major responsibility of the borrowing checking mechanism. Together with the life cycle mechanism, ensure that the references you use are valid.

Lifecycle annotations enable the borrowing check to determine whether a reference is valid. In most cases, the borrowing check mechanism can infer the reference life cycle by itself, but the explicit use of life cycle annotation in the code can enable the borrowing check mechanism to directly obtain the relevant information of reference validity.

This article will introduce the basic concepts and usage of life cycle, as well as some common scenarios. You need to have a certain understanding of the related concepts of rust (such as borrowing check).

Lifecycle flag

It should be noted that the flag of the rust life cycle is different from that in other languages. It is expressed in single quotation marks before the variable name, usually in lowercase letters:'a'bwait.

Why life cycle

Why does rust need such a strange feature? The answer lies in rust’s ownership mechanism. Borrowing check manages the allocation and release of memory, and ensures that there are no wrong references to the released memory. Similarly, the life cycle is also checked at the compilation stage. If there are invalid references, it will not pass the compilation.

In the two scenarios of function returning reference and creating reference structure, the life cycle is particularly important and easy to make mistakes.

example

The lifecycle is essentially a scope. When a variable leaves the scope, it will be released, and any reference to it will become invalid. The following is the simplest example copied from official documents:

//This code * * cannot * * be compiled
{
    let x;
    {                           // create new scope
        let y = 42;
        x = &y;
    }                           // y is dropped

    println!("The value of 'x' is {}.", x);
}

This code contains two different scopes inside and outside. When the scope inside is finished,yReleased, even ifxIt is declared in the outer scope, but it is still an invalid reference. The value it refers to “does not survive long enough”.

In life cycle terms, internal and external scopes correspond to one'innerAnd one'outerScope, the latter is significantly longer than the former, when'innerWhen the scope ends, all variables within it that follow its lifecycle become unavailable.

Life cycle omission

When writing functions that accept reference type variables as parameters, the compiler can automatically deduce the life cycle of variables in most scenarios without manual annotation. This situation is called “life cycle omission”.

The compiler uses three rules to confirm the function signature, which can omit the life cycle:

  • The return value of the function is not a reference type
  • There can be at most one reference in the input parameter of a function
  • Function is a method(method), that is, the first parameter is&selfperhaps&mut self

Examples and FAQs

Life cycle can easily confuse your mind. A simple introduction to a lot of words may not make you understand how it works. The best way to understand it is, of course, in programming practice and in the process of solving specific problems.

Function return reference

In rust, if a function does not receive a reference as a parameter, it cannot return a reference type. Forced return cannot be compiled. If there is only one reference type in the function parameter, the annotation life cycle will not be displayed. All reference types in the output parameter will be regarded as having the same life cycle as those in the input parameter.

fn f(s: &str) -> &str {
    s
}

But if you add another reference type, even if it is not applicable inside the function, the compilation will not pass. This reflects the second rule above.

//This code won't compile
fn f(s: &str, t: &str) -> &str {
    if s.len() > 5 { s } else { t }
}

When a function accepts multiple reference parameters, each parameter has its own life cycle. The compiler cannot automatically deduce which reference the function returns. For example, in the following code'???Which annotation life cycle will it be?

//This code won't compile
fn f<'a, 'b>(s: &'a str, t: &'b str) -> &'??? str {
    if s.len() > 5 { s } else { t }
}

Imagine, if you want to use the reference returned by this function, what kind of life cycle should you assign to it? Only by giving the parameter with the shortest life cycle can it be effective, and the compiler knows that they are references with the same and shorter life cycle. If, like the above function, all input parameters may be returned, you can only ensure that their life cycles are all the same, as follows:

fn f<'a>(s: &'a str, t: &'a str) -> &'a str {
    if s.len() > 5 { s } else { t }
}

If the input parameters of a function have different life cycles, but you know exactly which one you will return, you can mark the corresponding life cycle to the return type, so that the difference in the input parameter life cycle will not cause problems:

fn f<'a, 'b>(s: &'a str, _t: &'b str) -> &'a str {
    s
}

Structure reference

The life cycle problem of structure reference will be more difficult. It is better to replace it with non reference type, so you don’t have to worry about reference validity, life cycle duration and so on. In my experience, this is usually what you want.

However, some scenarios do need structure references, especially when you want to write a code package that does not need data ownership transfer and data copy. Structure references can enable the original data to be accessed elsewhere by reference without dealing with the thorny data crony problem.

For example, if you want to write code, look for the first and last two sentences of a text and store them in a structureSYes. If you do not use data copies, you need to use reference types and mark them with their life cycles:

struct S<'a> {
    first: &'a str,
    last: &'a str,
}

If the paragraph is empty, returnNone, if the paragraph has only one sentence, it will be returned from beginning to end:

fn try_create(paragraph: &str) -> Option<S> {
    let mut sentences = paragraph.split('.').filter(|s| !s.is_empty());
    match (sentences.next(), sentences.next_()) {
        (Some(first), Some(last)) => Some(S { first, last }),
        (Some(first), None) => Some(S { first, last: first }),
        _ => None,
    }
}

Because this function complies with the principle of automatic derivation of the life cycle (such as the return value is not a reference type and only receives at most one reference input parameter), it does not need to label the life cycle manually. If you want to not change the ownership of the original data, you can only consider entering a reference parameter to solve the problem.

summary

This article only briefly introduces the life cycle in rust. Considering its importance, I recommend reading the official documents again“Reference validity and lifecycle”Section, supplement more conceptual understanding.

If you still want to advance, it is recommended to watch Jon gjengset’s:Crust of Rust: Lifetime Annotations, the video contains examples of multiple life cycles. Of course, there are also some life cycle introductions, which are worth watching.

Recommended Today

Big data Hadoop — spark SQL + spark streaming

catalogue 1、 Spark SQL overview 2、 Sparksql version 1) Evolution of sparksql 2) Comparison between shark and sparksql 3)SparkSession 3、 RDD, dataframes and dataset 1) Relationship between the three 1)RDD 1. Core concept 2. RDD simple operation 3、RDD API 1)Transformation 2)Action 4. Actual operation 2)DataFrames 1. DSL style syntax operation 1) Dataframe creation 2. SQL […]