What can I learn from this nop function?

The following code will not compile , if I use the version without lifetime annotation (line A) , it will be ok , so how the lifetime annotation influence the compiler in this case?

fn main() {
  let mut a=0;
  let mut c=1;  
  let mut x=&mut a;
  let z=&mut c;
  fn nop<'a>(x:&mut &'a mut u8, z:&'a mut u8) {
//fn nop    (x:&mut &   mut u8, z:&   mut u8) {  // A
  nop(&mut x, z);
  *x = *z;    

The lifetime annotations say that both x and z must have exactly the same lifetime. But when you invoke nop(), the &mut c and the &mut a in &mut &mut a have different lifetimes because they come from different variables defined on different lines.

1 Like

When you tied together the lifetimes of the borrow of a in x and the reborrow of z, the compiler can see the possibility that inside of nop, you transfer z to replace the borrow of a with the reborrow of z (a borrow of c):

fn nop<'a>(x: &mut &'a mut u8, z: &'a mut u8) {
    // The lifetimes are the same so this is valid
    *x = z;

You didn't do this, but remember, functions are analyzed based on their signatures and not their bodies.

So, as per the function signature, x has "become infected" with z's reborrow, and vice-versa (since the lifetime is equal)(Edit: maybe not due to the nesting, I'd have to think on this one a bit more). Or put differently, any further use of x extends the reborrow of z.

Moreover, types underneath of a &mut are invariant. That means that the length of the borrow of a (in let x = ...) must be 'a, and the length of the reborrow of z must also be 'a. And so 'a must last until the assignment *x = ... completes.

So any use of z before the assignment completes will be an error. This includes reading the value in *z in an attempt to assign it to *x.

If you break things out a bit more, you can see how the assignment to *x is considered a use of the reborrow of z.

As far as I'm aware, this "infectious" property is not well documented; it makes sense, but you have to sort of work out the reasons. (If someone has a citation, I'd welcome it.)

On a different level that doesn't match the error, but is still a valid point I believe:

  • As mentioned, 'a must be the lifetime of the borrow of a due to invariance
  • So again, this must last until the assignment to *x completes
  • And because you named it so, 'a must also be the lifetime of the reborrow of z
  • And thus the original z cannot be used until after the assignment completes

The commented version of nop works because it removes the constraint that some lifetimes are equal or related at all; there would be no way to perform *x = z in the function for example, because the reborrow of z may not be long enough (try it to see the error). And in fact, it must not be long enough for the program to compile. The borrow of a must still be long enough for the assignment, but the reborrow of z can be shorter -- just as short as the call itself.

Rephrasing, having no named lifetimes implies that the lifetimes of all parameters are independent and unrelated (beyond being valid for the call itself).


Revisiting this since it was bugging me -- this statement isn't completely true.

  • There are implied bounds with types like &mut &mut Whatever
    • The inner borrows lasts at least as long as the outer borrow
  • Lifetime elision can indicate a relation between an input lifetime and an output lifetime
  • Various other niche circumstances like &dyn Trait /* + 'lt */

...but in this example specifically, the statement gives the right intuition.