Why does RefMut contain a NonNull<T>?

I'm investigating the machine code that is generated when RefCells are involved. RefMut contains a NonNull, with a comment explaining its purpose:

pub struct RefMut<'b, T: ?Sized + 'b> {
    // NB: we use a pointer instead of `&'b mut T` to avoid `noalias` violations, because a
    // `RefMut` argument doesn't hold exclusivity for its whole scope, only until it drops.
    value: NonNull<T>,
    borrow: BorrowRefMut<'b>,
    // `NonNull` is covariant over `T`, so we need to reintroduce invariance.
    marker: PhantomData<&'b mut T>,
}

The comment isn't clear to me - how could a RefMut's whole scope extend past the time it is dropped? What sort of code would fail if &'b mut T was used instead of NonNull<T>?

The NonNull<T> is preventing machine code optimizations that I hoped would be present in my project that uses RefCells.

1 Like

By scope they mean the code covered by an LLVM noalias annotation (a function body, I think). See the PR and (for an example) the issue it fixed.

4 Likes

When you write this code:

fn foo(arg: &mut T) {
}

then the compiler's assumption is that the mutable reference has exclusive access for the duration of the entire function. That's fine for mutable references, but for example it might not be true for other kinds of pointers:

fn foo(arg: Box<T>) {
    // do stuff
    drop(arg);
    // now arg is invalid
}

With a Box you have exclusive access too, but the access might not be valid for the entire duration of the function. This is because it has a destructor that will invalidate the reference. This is why Box does not get the same LLVM annotations as &mut even though both are exclusive.

The reason that RefMut does not use a &mut T internally is that it's more like Box<T> than it's like &mut T. It has a destructor that causes it to no longer have exclusive access.

13 Likes