Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this value not live long enough?

Tags:

I don't understand why I am getting the following compiler error from this code:

struct Superhero<'a> { name: &'a String, power: &'a i32 } //  1    
                                                          //  2    
fn main() {                                               //  3    
    let n = "Bruce Wayne".to_string();                    //  4    
    let r;                                                //  5    
    {                                                     //  6    
        let p = 98;                                       //  7    
        {                                                 //  8    
            let hero = Superhero{ name: &n, power: &p };  //  9    
            r = hero.name;                                // 10    
        }                                                 // 11    
        println!("{}", r);                                // 12    
    }                                                     // 13    
}                                                         // 14    

Compiler Error: rustc 1.27.1 (5f2b325f6 2018-07-07)

error[E0597]: `p` does not live long enough
  --> src/main.rs:9:53
   |
9  |             let hero = Superhero{ name: &n, power: &p };
   |                                                     ^ borrowed value does not live long enough
...
13 |     }
   |     - `p` dropped here while still borrowed
14 | }
   | - borrowed value needs to live until here

Here is what I thought this code would do, line by line. There is something wrong with one or more of these lines, because this code does not compile.

 4: Initialize name: String to "Bruce Wayne".to_string();
 5: Declare r to be initialized in a different scope
 6: Begin a new scope (A)
 7: Initialize p to 98
 8: Begin a new scope (B)
 9: Initialize hero: Superhero to a new struct
       hero.name refers to the variable{n},
       hero.power refers to the variable{p}  
10: Copy a reference to the variable{n}, 
       since reference types are copy, both hero.name and r are distinct references to the variable{n}.
11: End scope (B): hero and the two references it owns {hero.name, hero.power} are dropped.
12: Print the value of the variable{r}: Should print "Bruce Wayne"
13: End scope (A): the variable{p} is dropped.
14: End scope for main. The variables {n, r} are dropped.

Why does the compiler error say that something is still borrowing p on line 13? Shouldn't hero (and subsequently hero.power) have been dropped on line 11? There should be nothing referring to p at this point.

Curiously, changing the order in which the values (p and r) are initialized fixes the issue, and I have no idea why.

Ways to fix:

  1. Move line 7 let p = 90; between line 4 and line 5
  2. Move line 5 let r; between line 7 and line 8

In both of these cases, simply declaring r AFTER p ensures that nothing is still "borrowing" p when it is dropped. This makes no sense to me at all, because I feel like r has nothing at all to do with p, or with anything that may be borrowing p.

This code runs with non-lexical lifetimes enabled.

What property of lexical lifetimes causes this to not compile, and what about non-lexical lifetimes fixes this issue?

like image 316
ObliqueMotion Avatar asked Jul 12 '18 00:07

ObliqueMotion


1 Answers

This is only a guess, but here is what I think happens:

  • When you declared Superhero, you stated that name and power should have the same lifetime.
  • The type for r is inferred as &String (or maybe &str, the point is that r is a reference). With lexical lifetimes, r must live until the end of the block in which it is declared, so until line 14.
  • Since you affect hero.name to r, hero.name should live at least as long as r, therefore hero.name should live until line 14.
  • Since hero.name and hero.power should have the same lifetime per the struct declaration, hero.power should also live until line 14.
  • Since hero.power borrows p, p should live until line 14, but it only lives until the end of the block in which it is declared (line 13).

The reason it works with non-lexical lifetimes is because the compiler notices that you don't use r after line 12 and is therefore able to shorten the lifetimes accordingly. Note that it doesn't work even with nll if you use r after the closing brace of line 13.

like image 116
Jmb Avatar answered Sep 28 '22 19:09

Jmb