I don't have a particularly solid understanding of Rust's aliasing rules (and from what I've heard they're not solidly defined), but I'm having trouble understanding what makes this code example in the std::slice
documentation okay. I'll repeat it here:
let x = &mut [1, 2, 4];
let x_ptr = x.as_mut_ptr();
unsafe {
for i in 0..x.len() {
*x_ptr.offset(i as isize) += 2;
}
}
assert_eq!(x, &[3, 4, 6]);
The problem I see here is that x
, being an &mut
reference, can be assumed to be unique by the compiler. The contents of x
get modified through x_ptr
, and then read back via x
, and I see no reason why the compiler couldn't just assume that x
hadn't been modified, since it was never modified through the only existing &mut
reference.
So, what am I missing here?
Is the compiler required to assume that
*mut T
may alias&mut T
, even though it's normally allowed to assume that&mut T
never aliases another&mut T
?Does the
unsafe
block act as some sort of aliasing barrier, where the compiler assumes that code inside it may have modified anything in scope?Is this code example broken?
If there is some kind of stable rule that makes this example okay, what exactly is it? What is its extent? How much should I worry about aliasing assumptions breaking random things in unsafe
Rust code?
Disclaimer: there is no formal memory model, yet.1
First of all, I'd like to address:
The problem I see here is that
x
, being an&mut
reference, can be assumed to be unique by the compiler.
Yes... and no. x
can only be assumed to be unique if not borrowed, an important distinction:
fn doit(x: &mut T) {
let y = &mut *x;
// x is re-borrowed at this point.
}
Therefore, currently, I would work with the assumption that deriving a pointer from x
will temporarily "borrow" x
in some sense.
This is all wishy washy in the absence of a formal model, of course, and part of the reason why the rustc compiler is not too aggressive with aliasing optimizations yet: until a formal model is defined, and code is checked to match it, optimizations have to be conservative.
1The RustBelt project is all about establishing a formally proven memory model for Rust. The latest news from Ralf Jung were about a Stacked Borrows model.
From Ralf (comments): the key point in the above example is that there is a clear transfer from x
to x_ptr
and back to x
again. So the x_ptr
is a scoped borrow in a sense. Should the usage go x
, x_ptr
, back to x
and back to x_ptr
, then the latter would be Undefined Behavior:
fn main() {
let x = &mut [1, 2, 4];
let x_ptr = x.as_mut_ptr(); // x_ptr borrows the right to mutate
unsafe {
for i in 0..x.len() {
*x_ptr.offset(i as isize) += 2; // Fine use of raw pointer.
}
}
assert_eq!(x, &[3, 4, 6]); // x is back in charge, x_ptr invalidated.
unsafe { *x_ptr += 1; } // BÄM! Used no-longer-valid raw pointer.
}
来源:https://stackoverflow.com/questions/52548161/why-does-modifying-a-mutable-references-value-through-a-raw-pointer-not-violate