The link is not long enough in the nested structure

I am creating a series of data structures containing volatile references to lower level structures. I worked quite happily with A , B and C below, but I tried to add a new layer D A , B , C , D are actually state machines for decoding the protocol, but I deleted it all here:

 struct A {} fn init_A() -> A { A {} } struct B<'l> { ed: &'l mut A, } fn init_B(mut e: &mut A) -> B { B { ed: e } } struct C<'l> { pd: &'l mut B<'l>, } fn init_C<'l>(mut p: &'l mut B<'l>) -> C<'l> { C { pd: p } } struct D<'lifetime> { sd: &'lifetime mut C<'lifetime>, } fn init_D<'l>(mut p: &'l mut C<'l>) -> D<'l> { D { sd: p } } fn main() { let mut a = init_A(); let mut b = init_B(&mut a); let mut c = init_C(&mut b); // COMMENT OUT THE BELOW LINE FOR SUCCESSFUL COMPILE let mut d = init_D(&mut c); } 

I get an error message:

 error[E0597]: `c` does not live long enough --> src/main.rs:38:1 | 37 | let mut d = init_D(&mut c); | - borrow occurs here 38 | } | ^ `c` dropped here while still borrowed | = note: values in a scope are dropped in the opposite order they are created 

I lack an understanding of what happens differently for D compared to C as I live: I don't understand what a mismatch is in life.

+5
source share
2 answers

I will consider why this code does not work.

TL DR: Invariance in the lifetime of types C<'l> and D<'l> , and the use of one parameter lifetime ( 'l ) for them leads to the fact that the variables of these types retain their values ​​as long as the variable b exists, but the variable c (borrowed d ) is discarded to b .

The borrowing controller is essentially a constraint solver. He searches for the shortest lifetimes 0 that satisfy various constraints: the link should not live longer than the value that it refers, the lifetimes must obey the restrictions indicated in the signatures and types of functions, and the lifetimes must obey the dispersion rules 1 .

0 . The shortest link lifetime is better, because then the link does not take longer than necessary.

1 . Rust has a concept of variance that defines whether a value with a longer lifetime can be used in a place that expects a value of a shorter lifetime. The Rustonomicon link explains this in detail.

The code below is a simplified version of the code in question, and it fails with the same error: c does not live long enough. Blocks are marked by the lifetime of the variables. 'a is the lifetime of the variable a , etc. These lifetimes are determined by the structure of the code, and they are fixed.

Times in annotations like ( B(&'ar A) -> B<'ar> etc.) are variable. The loan verification tool attempts to find reliable fixed-life assignments ( 'a , 'b , 'c ,' d ) for these variables.

The comments below let statements show lifetime limits, which I will explain below.

 struct A; struct B<'l>(&'l mut A); struct C<'l>(&'l mut B<'l>); struct D<'l>(&'l mut C<'l>); fn main() { // lifetime 'a let mut a = A; { // lifetime 'b // B(&'r mut A) -> B<'ar> let mut b = B(&mut a); // 'r >= 'ar & 'r <= 'a { // lifetime 'c // C(&'br mut B<'ar>) -> C<'abr> let mut c = C(&mut b); // 'br <= 'b & 'abr = 'ar & 'br >= 'abr { // lifetime 'd // D(&'cr mut C<'abr>) -> D<'cabr> let d = D(&mut c); // 'cr <= 'c & 'cabr = 'abr & 'cr >= 'cabr } } } } 

First appointment

 // B(&'r mut A) -> B<'ar> let mut b = B(&mut a); // 'r <= 'a & 'r >= 'ar 

A reference to a cannot survive a , hence 'r <= 'a .

&'r mut A is an option over' r, so we can pass it to a constructor of type B<'ar> , which expects &'ar mut A iff 'r >= 'ar .

Second appointment

  // C(&'br mut B<'ar>) -> C<'abr> let mut c = C(&mut b); // 'br <= 'b & 'abr = 'ar & 'br >= 'abr 

A link cannot survive b ( 'br <= 'b ), &mut B is invariant with respect to b ( 'abr = 'ar ), &'br mut B is an option over 'br ( 'br >= 'abr )

d appointment similar to c .

Rust does not seem to consider lifetimes that it has not yet met as possible destinations. Possible assignments for 'ar are 'a or 'b , for 'abr - 'a , 'b or 'c , etc.

This set of constraints comes down to 'ar = 'abr = 'cabr , and the smallest allowed assignment for 'ar is 'b . Therefore, types b , c and d : B<'b> , C<'b> , D<'b> . That is, the variable d contains a reference to c for the lifetime of 'b , but c discarded at the end of 'c the lifetime.

If we delete d , then c still saves the b borrowed to the end of the service life of 'b , but this is not a problem because b does not survive the lifetime of 'b .

This description is still simplified. For example, if type c is equal to C<'b> , c does not occupy b over the whole life of 'b , it will borrow it for part 'b , starting with the definition of c , but I still do not have a clear understanding.

+3
source

The init_*() functions in your source code always return a type with the lifetime parameter equal to the lifetime of the link in which you passed. Since you are creating a link chain in this way, all your lives will be the same, and types a , b , c , d will eventually be a , B<'a> , C<'a> , D<'a> . This is normal down to c , since the lifetime of 'a can be a region of b that satisfies all the constraints.

However, once you add d to the mix, there isn’t a single lifetime that will make all links valid. Life time 'a can no longer be b , since c not long enough. It also cannot be region c , since it is too short for b , so there are no compiler errors.

By decoupling the lifetimes, it is possible that all variables have their own lifetimes, and everything works as expected. Since the problem begins only with d , it is sufficient to introduce the additional lifetime at this point.

 struct A; fn init_a() -> A { A {} } struct B<'a> { ed: &'a mut A, } fn init_b(ed: &mut A) -> B { B { ed } } struct C<'b> { pd: &'b mut B<'b>, } fn init_c<'b>(pd: &'b mut B<'b>) -> C<'b> { C { pd } } struct D<'c, 'b: 'c> { sd: &'c mut C<'b>, } fn init_d<'c, 'b: 'c>(sd: &'c mut C<'b>) -> D<'c, 'b> { D { sd } } fn main() { let mut a = init_a(); let mut b = init_b(&mut a); let mut c = init_c(&mut b); let d = init_d(&mut c); } 

Playground

+2
source

Source: https://habr.com/ru/post/1274581/


All Articles