Search code examples
rustborrow-checkerownershipborrowing

Understanding non lexical lifetimes when calling functions that return reference


The following function works fine with regards to NLL

fn main() {
    let mut x = 1i32;
    let mut y = &mut x;

    let z = &mut y; 

    *y = 12;
}

However if I replace the statement let z = &mut y with a function call (that basically does the same thing), the borrow checker complains.

fn test<'a>(x:&'a mut &'a mut i32) -> &'a mut i32 {
    &mut **x
}

fn main() {
    let mut x = 1i32;
    let mut y = &mut x;

    let z = test(&mut y);

    *y = 12;
}

gives the following error:

error[E0506]: cannot assign to `*y` because it is borrowed
  --> src/main.rs:11:5
   |
9  |     let z = test(&mut y);
   |                  ------ borrow of `*y` occurs here
10 |     
11 |     *y = 12;
   |     ^^^^^^^
   |     |
   |     assignment to borrowed `*y` occurs here
   |     borrow later used here

The reference returned by the function test() is not used anymore, so shouldn't it be considered 'dead'?


Solution

  • Let's give some names to the lifetimes in your program. (This won't compile, but is for demonstrative purposes.)

    In your first example, we have get two lifetimes, '1 and '2. The lifetime '2 only lasts for a single line, so y can be used later on:

    fn main() {
        let mut x = 1i32;      //
        let mut y = &'1 mut x; //        ^
                               //        |
        let z = &'2 mut y;     //  |'2   |'1
                               //        |
        *y = 12;               //        v
    }
    

    In the second example, because test requires &'a mut &'a mut i32 where 'a represent identical lifetimes, the outer reference must live as long as the inner reference. This is why we get an error:

    fn test<'a>(x:&'a mut &'a mut i32) -> &'a mut i32 {
        &mut **x
    }
    
    fn main() {
        let mut x = 1i32;        //
        let mut y = &'1 mut x;   //        ^
                                 //        |
        let z = test(&'2 mut y); //  |'2   |'1
                                 //  |     |
        *y = 12;                 //  v     v
    }
    

    If we give test two distinct lifetimes, however, the code will now compile fine because we are back in the same situation as in the first example:

    fn test<'a, 'b>(x:&'a mut &'b mut i32) -> &'a mut i32 {
        &mut **x
    }
    
    fn main() {
        let mut x = 1i32;        //
        let mut y = &'1 mut x;   //        ^
                                 //        |
        let z = test(&'2 mut y); //  |'2   |'1
                                 //        |
        *y = 12;                 //        v
    }