My view on things looks a bit like an extension of the borrowing rules. If you get a reference, that reference is live. Any unsafe operations derived from this reference means you can’t use the reference again until you stop doing so. A reference derived from non-reference data borrows all of that data; if you want to perform unsafe operations you need to derive it once again from the reference, or wait until the reference is no longer used.
Note that the comment here is not about liveness of references, but of reads and writes. This is because unsafe code is about getting access to the logical model “below” the borrow checker, which is a behavioural thing. (This is my opinion of the “more fundamental underlying concept” mentioned earlier.)
So in this case:
fn patsy(v: &usize) -> usize {
let l = *v;
collaborator();
use(l);
}
We know v
guarantees us a borrow of the data. Although collaborator
may have pointers to it, those are also conceptually borrowed so it can only perform reads, not writes. collaborator
cannot re-derive a mutable pointer because the value is immutably borrowed. The optimisation is thus possible.
And in this case:
fn alloc_free() {
unsafe {
let p: *mut i32 = allocate_an_integer();
let q: &i32 = &*p;
let r = *q;
free(p);
use(r);
}
}
p
is “borrowed” (read: derived) by q
, so use of free(p)
is only legal with the guarantee that q
is never used again. The optimisation is thus not possible.
Now we’re getting to the complicated ones:
fn alloc_free2() {
unsafe {
let p: *mut i32 = allocate_an_integer();
let q: &i32 = &*p;
let r = *q;
if condition1() {
free(p);
}
if condition2() {
use(r);
if condition3() {
use_again(*q);
}
}
}
}
When free(p)
is called, we have a guarantee that q
cannot be used again, because if it was then p
would be borrowed and so cannot be mutated. But if condition3()
, then q
is used again. So the compiler can assume condition1() ⇒ !condition3()
. It cannot, however, assume q
is live when use(r)
is called.
The optimisation is thus not possible, though a better one is (use_again(*q)
→ use_again(r)
).
Note that this means
*(mut_ref as *mut _) = 0;
*mut_ref = 1;
*(mut_ref as *mut _) = 0;
is legal, but
let mut_ptr = mut_ref as *mut _;
*mut_ptr = 0;
*mut_ref = 1;
*mut_ptr = 0;
is not.