The Rust documentation for std::ptr has this rule:
The result of casting a reference to a pointer is valid for as long as the underlying object is live and no reference (just raw pointers) is used to access the same memory. That is, reference and pointer accesses cannot be interleaved.
I agree that pointers invalidate references. That is, if you use a pointer to access data while a reference to that data exists, sure, that's UB. But references invalidate pointers?
Is that right?
Why is that rule necessary? It seems like it makes programming with pointers super dangerous and error-prone, especially since the "casting" is done implicitly by the compiler, e.g. if you pass a reference to a pointer argument.
Does this mean that passing a pointer to this function invalidates the pointer?
My simple minded and vague answer is that the compiler (via LLVM) is allowed to do all sorts of radical optimizations with references that are not allowed with pointers. So mixing them in any form, for the same variable, is going to be dangerous. Having a rule that prohibits this is a really good idea.
Your code doesn't contain any "casting a reference to a pointer", so that paragraph doesn't literally apply, but also, I suspect the original text means to say that you can't interleave use of a particular mutable reference and pointers derived from that reference.
Certainly re-borrows from reference to pointer and back have to be sound, or many std data structures such as Vec would be unsound.
Here is some code that does both reference-to-pointer casts and pointer-to-reference casts, and lots of mutation with each. Miri agrees with me that it contains no UB, and I hope most Rust programmers with unsafe experience will agree with me that this is all reasonable code:
The above code (specifically foo) does coerce a reference to a pointer, use the pointer, and then use the reference, but it is sound. I think that what documentation means, but expresses poorly, is that you must not do the following:
fn main() {
let mut x = 1;
bad(&mut x);
}
fn bad(x: &mut u8) {
let p: *mut u8 = x;
*x += 1;
unsafe { *p += 1; }; // UB because we used the `x` that we coerced from
}
I think that by “interleaving”, they meant a sequence of operations that cannot be described as strictly nested (each reborrow or coercion being dropped (or more precisely, no longer used) before its origin is used again), the way the first program sticks to.
Based on @kpreid's response (which seems right!) even changing the word "interleaved" to "overlapping" would help. And to add his words:
... That is, reference and pointer accesses must be non-overlapping, by which we mean each reborrow or coercion is dropped (or more precisely, no longer used) before its origin is used again.
fn main()
{
let mut x = 0;
let r = &mut x;
let p : * mut i32 = r;
*r = 4;
unsafe{ *p = 5; }
println!("r={}", r)
}
7 | unsafe{ *p = 5; }
| ^^^^^^
| |
| attempting a write access using <2383> at alloc1160[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of an access at alloc1160[0x0..0x4]