The following example of how one might try to implement a self-referential struct is unsound.
use std::cell::UnsafeCell;
use std::pin::Pin;
use std::marker::PhantomPinned;
struct SelfRef {
value: UnsafeCell<u64>,
ptr: *const u64,
_pin: PhantomPinned,
}
impl SelfRef {
fn new(value: u64) -> Self {
Self {
value: UnsafeCell::new(value),
ptr: std::ptr::null(),
_pin: PhantomPinned,
}
}
fn set_ptr(self: Pin<&mut Self>) {
let me = unsafe { Pin::into_inner_unchecked(self) };
let ptr = &mut me.ptr;
let value = me.value.get();
*ptr = value;
}
fn get_value(self: Pin<&mut Self>) -> u64 {
let me = unsafe { Pin::into_inner_unchecked(self) };
assert!(!me.ptr.is_null());
unsafe { *me.ptr }
}
}
fn main() {
let self_ref = SelfRef::new(10);
futures::pin_mut!(self_ref);
self_ref.as_mut().set_ptr();
println!("{}", self_ref.get_value());
}
If you run the above with miri, you will see that it encounters UB. As I understand it, this is because when we call get_value
, we create a unique reference to SelfRef
, and hence we assert exclusive access to the struct, including all of its fields. This invalidates the *const u64
we stored in the struct, as it was created before we asserted unique access to value
.
Note that in the above I use that an &mut UnsafeCell<T>
still asserts exclusive access to the T
. As far as I can see, this is indeed the case. The assumption that UnsafeCell
turns off is the opposite, namely that an &UnsafeCell<T>
does not assert that the T
is immutable.
What does the compiler even do when it constructs a future with async/await? As far as I can see there is simply no way to fix the above code.
(Note that if we allow boxing of the value
field, it is ok as the &mut SelfRef
does not assert exclusive access through raw pointers recursively.)