Your r
is a shared reference to b
: from the pointer where it is assigned, up until the point of last use, there can only be other shared accesses to b
, there can't be exclusive accesses to b
.
At line b = ...
, you assign to b
, which requires unique access to b
. This contradicts the outstanding shared borrow r
of b
, unless that shared borrow is not used anymore.
Another way of seeing this is that when performing the assignment, since it requires unique / exclusive access to b
, any outstanding reference gets "invalidated" / deactivated. If you try to use it (as in the println!("{}", r);
statement), then the borrow checker will trigger a compilation error.
When you do r = &b;
, however, you are not using a stale / invalidated pointer (you are not reading its value) but rather, you are (re-)updating the borrow as a new one (new point of assignment), which then reasserts, from that point onwards, lack of exclusive access to b
... until the point of last use. And so on and so forth.
let mut b = Box::new(99);
let mut r = &b; // ----------+ shared borrow starts here and spans until...
let i = 0; { // |
println!("{}", r); // <--+ ...the point of last use
b = Box::new(i); // ⚠️ exclusive access must not collide with shared one ✅
r = &b; // ----------+ shared borrow resumes here and spans until...
} // |
let i = 1; { // |
println!("{}", r); // <--+ ...the point of last use
b = Box::new(i); // ⚠️ exclusive access must not collide with shared one ✅
r = &b; // ----------+ shared borrow resumes here and spans until...
} // |
...
let i = 9; { // |
println!("{}", r); // <--+ ...the point of last use
b = Box::new(i); // ⚠️ exclusive access must not collide with shared one ✅
r = &b; // ----------+ shared borrow resumes here and spans until...
} // |
println!("{}", r); // <------+ ...the point of last use
So this is a fine snippet.
But if you commented out that re-assignment line, then you'd be having a single very largely spanned borrow, which would necessarily overlap over that incompatible assignment:
let mut b = Box::new(99);
let mut r = &b; // ----------+ shared borrow starts here and spans until...
let i = 0; { // |
println!("{}", r); // |
b = Box::new(i); // excl💥sive access must not collide with shared one
} // |
let i = 1; { // |
println!("{}", r); // |
b = Box::new(i); // excl💥sive access must not collide with shared one
} // |
... // |
let i = 9; { // |
println!("{}", r); // |
b = Box::new(i); // excl💥sive access must not collide with shared one
} // |
println!("{}", r); // <------+ ...the point of last use
Aside
It is interesting to see that all these assignments are being done to the same variable r
, which ought to have the lifetime embedded inside its type. So this means that the "lifetime parameter appearing in the type of r
", if such a thing existed, is representing a non-continuous region of code! It's quite fascinating, while also something that cannot be expressed, at least not before polonius, when using actual generic lifetime parameters at function boundaries (an attempt)