FYI, a good way to know if something is sound is to try and write the pointer code only using Rust references; and seeing *mut T
as &UnsafeCell<T>
(aliased upgradable-to-unique-on-demand pointer):
-
impl<T : ?Sized> /* SomeExtensionTrait<T> for */ UnsafeCell<T> { /// Downgrade the uniqueness guarantee to only be applicable during /// the `'uniq` spans, instead of continually until the point of last usage. fn from_mut (it: &'_ mut T) -> &'_ UnsafeCell<T> { unsafe { ::core::mem::transmute(it) } } /// Safety: of all the `&UnsafeCell`s aliasing the pointee, /// none may be in an upgraded `&mut T` or even `&T` form /// for the duration of `'uniq` unsafe fn assume_unique<'uniq> (self: &'uniq UnsafeCell<T>) -> &'uniq mut T { &mut *self.get() } }
With this, we can rewrite:
fn main() {
let test = &mut 77f32;
{
let ptr1 = test as *mut f32;
let ptr2 = test as *mut f32;
unsafe {
*ptr2 = 32f32;
};
}
assert_eq!(*test, 32f32);
}
as:
fn main() {
let test = &mut 77f32;
{
let ptr1: &Cell<f32> = Cell::from_mut(test);
let ptr2: &Cell<f32> = Cell::from_mut(test);
// if ptr1 were used here, we'd get a borrock error.
ptr2.set(32f32);
}
assert_eq!(*test, 32f32);
}
- And we can see why using
ptr1
would fail there
Whereas the ptr2 = ptr1
version would be:
fn main() {
let test = &mut 77f32;
{
let ptr1: &Cell<f32> = Cell::from_mut(test);
let ptr2: &Cell<f32> = ptr1; // OK, does not invalidate `ptr1`
unsafe {
ptr1.set(32f32); // OK
};
}
assert_eq!(*test, 32f32);
}
We can now apply this logic to @2e71828's examples:
fn f1<'a> (ra: &'a mut i32) -> &'a mut i32
{
let p1: &'_ UnsafeCell<i32> = UnsafeCell::from_mut(ra);
let p2 = p1; // OK
unsafe {
*p1.assume_unique() += 2; // `'uniq` one-line span for `p1`: OK
*p2.assume_unique() += 2; // same for `p2`
*p1.assume_unique() += 2; // same for `p1`
}
ra // OK: using `ra` invalidates `p1` and `p2`, but they are no longer usable anyways.
}
as well as:
fn f2<'a> (ra: &'a mut i32) ->&'a mut i32
{
let p1: &'a UnsafeCell<i32> = UnsafeCell::from_mut(ra); // (re)borrow -----+
std::mem::drop(ra); // Invalidates `p1`: (re)borrowed value dropped here |
let p2 = p1; // borrow used here <-----------------------------------------+
unsafe {
*p1 += 2;
*p2 += 2;
*p1 += 2;
&mut *p2
}
}
and
fn f2b<'a> (ra: &'a mut i32) -> &'a mut i32
{
let p1: &'a UnsafeCell<i32> = UnsafeCell::from_mut(ra);
let p2 = p1;
unsafe {
*p1.assume_unique() += 2; // OK
*p2.assume_unique() += 2; // OK
*p1.assume_unique() += 2; // OK
p2.assume_unique() // `'uniq = 'a` here, OK because `p1` is never upgraded, and `ra` is never used.
}
}
Aside
FWIW, I'm considering making a PR to suggest those two functions
- (+ some name-bikesheddable
unsafe fn assume_no_mut<'no_mut> (self: &'no_mut UnsafeCell<T>) -> &'no_mut T
; that's the one I can't come up with a good name for it)
be added to UnsafeCell<T>
, since they would improve the teachability of UnsafeCell<T>
, interior mutability and Stacked Borrows model, and would also lead to less dangerous code (manually upgrading .get()
is harder to read: it is harder to notice that the invariants to uphold are different for .assume_unique()
than for .assume_no_mut()
if the author is, instead, using &mut* ....get()
and &* ...get()
each time).