Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does modifying a mutable reference's value through a raw pointer not violate Rust's aliasing rules?

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?

like image 595
lcmylin Avatar asked Sep 28 '18 04:09

lcmylin


1 Answers

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.
}
like image 115
Matthieu M. Avatar answered Oct 13 '22 20:10

Matthieu M.