Is it fine to have multiple mutable pointers to something if I don't dereference more than one?

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).

5 Likes