Intrigue with rust · it’s time to mark the life cycle

Time:2022-5-27

Rskynet is just a small project with more than 300 lines of code… Or an extremely small project. Its function is to read the off file recording polyhedral information, calculate the polyhedral bounding sphere, semi automatically generate the model and view files in POV ray scene based on the center of the bounding sphere, and submit them to Povray for analysis to generate the rendering results of specified viewing angle for polyhedron. However, these functions are almost irrelevant to this article, but only in the process of realizing these functionsMeshWhen defining a method for a structure, you encounter the need to explicitly label the life cycle.

The problem should be simplified

Suppose there is a structure

struct Foo {
    name: &str,
    value: i32,
}

If the following code is used to constructFooExamples of

let x = Foo {name: "foo", value: 1};

Rustc will reluctantly point out

error[E0106]: missing lifetime specifier
  ... ... ...
  |
  |     name: &str,
  |           ^ expected named lifetime parameter

Suggestions will also be given

help: consider introducing a named lifetime parameter
  |
  ~ struct Foo<'a> {
  ~     name: &'a str,
  |

In accordance with the above recommendations, theFooThe definition of is modified to

struct Foo<'a> {
    name: &'a str,
    value: i32,
}

The problem has been solved. So far, I have witnessed rust’s great invention – life cycle annotation, but what happened?

Lifecycles are generic types

In the modifiedFooInside,'aAppears in the generic parameters I’m familiar withTWhere it appears, is it a generic parameter? I guess so. Everything is different, but time is the same. In the rust language, every reference has a life cycle. Using expired references (the referenced variables have been released) will cause the code to be rejected by rustc. The ideal is good, but rustc sometimes cannot determine whether a reference is out of date. for example

let x = Foo {name: "foo", value: 1};

String"foo"A reference toFooofnameMember variable, rustc cannot determinexDuring the life cycle of the string"foo"Still alive. in my opinion,"foo"It is directly encoded in the executable file of the program. It has the same life as the program, so it is impossible toxBut in the view of rustc, it only knows that this is a string reference, and as long as it is a reference, there may be a risk of referencing expired variables, so it needs me to tell it clearly,"foo"How long can you live.

Lifecycle Tags

It is basically impossible to tell rustc how long the life cycle of a variable is, but it can tell rustc through the life cycle mark that the life cycle of a variable is at least equal to a certain life cycle. for example

struct Foo<'a> {
    name: &'a str,
    value: i32,
}

Can tell rustc,nameThe referenced variable can at least live as long asFooAs long as, in fact, it is.

A lifecycle tag is only a constraint on the timeliness of a reference. It does not change the lifecycle of the reference. The following code can illustrate this,

let mut x = Foo {name: "", value: 1};
{
    let a = String::from("foo");
    foo.name = a.as_str();
}
println!("({}, {})", foo.name, foo.value);

astay{ ... }It survives in the local action area and will be released out of this area. althoughaPart of the content (string slice) isfoo.nameReference, and the life cycle mark requires that the life cycle of this part of the content is at least the same asfooEqual, but this is not the case, so rustc will refuse this code to compile and give the following error message

error[E0597]: `a` does not live long enough
   ... ... ...
   |
   |         foo.name = a.as_str();
   |                    ^^^^^^^^^^ borrowed value does not live long enough
   |     }
   |     - `a` dropped here while still borrowed
   |     println!("({}, {})", foo.name, foo.value);
   |                          -------- borrow later used here

Old grievances

I was writing last month last yearrhamal.pdf, in section 2.8, I encountered a supernatural event that the following code could not be compiled:

struct Point {x: f64, y: f64, z: f64}

fn main() {
    let mut a = Point {x: 1.0, y: 2.0, z: 3.0};
    let b = &mut a;
    println!("({}, {}, {})", a.x, a.y, a.z);
    println!("({}, {}, {})", b.x, b.y, b.z);
}

At that time, I was so afraid of rust grammar that I didn’t have the patience to observe the error reported by rustc, but took it for granted that it might be related toaandbRelated to inconsistent life cycles. Now, the life cycle can be overturned.

The error of the above code is that,acoverbVariable borrowing, and it is variable borrowing, and in the subsequentprintln!In the statement,aIt is in the form of immutable borrowing——println!Is a macro whose parameters are automatically converted to immutable borrowing. In the rust syntax, if a variable is borrowed immutably after being treated as a variable, it can no longer be accessed through a variable reference. On the contrary, if a variable is borrowed immutably first and then borrowed variably, this is allowed – probably avoiding data competition. Therefore, the above code needs to be modified to

struct Point {x: f64, y: f64, z: f64}

fn main() {
    let mut a = Point {x: 1.0, y: 2.0, z: 3.0};
    let b = &mut a;
    println!("({}, {}, {})", b.x, b.y, b.z);
    println!("({}, {}, {})", a.x, a.y, a.z);
}

Summary

No longer too afraid of lifecycle markers.