Hey!
Take a look at the following code.
fn main() {
let v: Vec<i32> = vec![0i32];
// We're using `&v` here, and we're getting a `*const i32` back.
let ptr_to_first = v.as_ptr() as *mut i32;
unsafe {
// Safety:
// `v` is owned by this function. `ptr_to_first` points into the
// vector's owned memory, ensuring we have unique access to it.
*ptr_to_first = 10i32;
}
// Mutation did occur even though `v` is not marked as mutable
// (i.e. `let v` and not `let mut v`).
println!("{}", v[0]);
}
At first, I had assumed this code unsound because we're basically mutating the vector through a shared reference (v.as_ptr()
takes &self
).
However, according to what I understand of Stacked Borrows, this code should be sound.
For heap allocations, the stack of each freshly allocated memory location is
Stack { borrows: vec![(Untagged: SharedReadWrite)] }
, and the initial pointer to that memory has tagUntagged
. (ref)
When we use v.as_ptr()
to get a pointer to the allocation, the stack-memory that stores v
is borrowed, but the actual heap location is left untouched. When the pointer to that location is used to write 10i32
, the allocated memory is still a SharedReadWrite
and not a SharedReadOnly
like v
.
When I run miri (see playground), it detects no undefined behaviour. Is this intended? Or more generally, can I rely on this (allocated memory acting like UnsafeCell
)?
I'm probably misunderstanding something here, because Stacked Borrows isn't exactly easy to understand. If I do, please explain to me why this is, or is not, sound.
EDIT: Here is an example where this could be useful: playground