Why use mutable reference before its generated raw pointer is UB?

fn main() {
    unsafe {
        let mut data = 0;
        let a = &mut data;
        let b = a as *mut i32;
        *a = 1;
        *b = 10; // <= miri error

        *a = 1;
    }
}

Codes above violate the stacked borrow rule, and miri complains about that. But what bad things could happen when using this raw pointer after the first use of its original reference?

As a is alive in whole scope, shouldn't b's mutable access to data be valid?

miri message here:

Writing to a mutable reference reasserts its uniqueness, that is the fact that no other pointer has access to it (except those in the stack before that mutable variable, in this case b was added to the stack after).

A potential unexpected transformation that could be done with this information is to remove the last *a = 1. To understand why, consider the last 3 instructions:

*a = 1;
*b = 10;
*a = 1;
  • there is a write to a, which asserts that a is the only way to access its backing data;
  • then there is a write to b, which hence must write to different data (this is wrong! but that's because you caused UB)
  • then again a write to a of the same value

Considering only writes that can modify the backing data of a, there are two such writes and both write 1, so the optimizer is allowed to omit the second one which will surely not be observable (it is actually observable but only because you caused UB). Hence the code can end up being the same as:

*a = 1;
*b = 10;
3 Likes

Or, conversely, it might remove the first *a = 1 - since the value is never read and is overwritten later anyway.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.