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).