Nested mut reference lifetime

I'm trying to fix a confusing lifetime issue. I reduced it down to this minimal example:

fn go<'a, 'b>(value: &'a mut &'b mut i32) -> &'b mut i32 {
    *value
}

It doesn't compile:

1 | fn go<'a, 'b>(value: &'a mut &'b mut i32) -> &'b mut i32 {
  |                      -------------------     -----------
  |                      |
  |                      this parameter and the return type are declared with different lifetimes...
2 |     *value
  |     ^^^^^^ ...but data from `value` is returned here

All I'm doing is dereferencing and returning the type with the same lifetime. Why is that not valid?

You need to specify that 'a: 'b, or in other words, 'a lasts at least as long as 'b.

fn go<'a, 'b>(value: &'a mut &'b mut i32) -> &'b mut i32
where
    'a: 'b,
{
    *value
}

If your function were accepted then you could duplicate an &mut reference.

let data = 100;
let b = &mut data;  // suppose this has lifetime 'b

let borrow1 = go /* <'a1, 'b> */ (&mut b); // borrows b with temporary lifetime 'a1
let borrow2 = go /* <'a2, 'b> */ (&mut b); // borrows b with temporary lifetime 'a2

// borrow1 and borrow2 are now &'b mut i32 to the same data,
// and they have forgotten about 'a1 and 'a2

&mut references are unique references. The dereference operation in go() violates that uniqueness by allowing returning a &'b mut which lives longer than the &'a mut which granted access to it.

@RedDocMD's suggestion will work, by forcing 'a to be the same length as 'b, which ensures the above problem doesn't happen because the lifetimes of borrow1 and borrow2 become equal to their respective (&mut b)s, at which point the usual mutable borrow conflict rules apply.

Another approach which makes the function as general as possible while still succeeding is

fn go<'a, 'b>(value: &'a mut &'b mut i32) -> &'a mut i32 {

Now, 'b can still be longer than 'a, but the returned borrow is only as long as it can be while still preserving uniqueness of &mut references — as long as the input &'a mut.

5 Likes

Thanks, that explains it. I'm not sure how I couldn't see that on my own :laughing:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.