Does UnsafeCell<T> make the coexistence of &mut T legal?

I’m trying to verify my understanding of Rust’s aliasing rules regarding UnsafeCell<T>.

It seems that if you have multiple &UnsafeCell<T> references and use one to create a temporary &mut T for writing, you inevitably have both types of pointers pointing to the same memory at the same time.

My reasoning is that since UnsafeCell<T> explicitly opts out of the standard &T immutability/noalias guarantees, this "coexistence" might actually be legal within the memory model. Is it correct to say that as long as UnsafeCell is involved, the strict exclusivity of &mut T is relaxed to allow these overlaps?

I’d appreciate any insights on whether this interpretation is correct.

No, that's not correct.

Note that only the immutability guarantee for shared references is affected by UnsafeCell. The uniqueness guarantee for mutable references is unaffected. There is no legal way to obtain aliasing &mut, not even with UnsafeCell<T>.

For the possibility of relaxing the exclusiveness of &mut _ in the future, see UnsafePinned. (As the name implies, it's motivation is a weakness in the Pin model.)

1 Like

UnsafeCell can be thought of as an “interior boundary” of what is made immutable by &: if you have &(Foo, UnsafeCell<Bar>), then the immutability applies to Foo but not to Bar.

    ┏━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃                 ┏╍╍╍┓  ┃ 
   &┃(Foo, UnsafeCell<┋Bar┋>)┃
    ┃                 ┋   ┋  ┃
    ┃                 ┋   ┋  ┃
not ┃    immutable    ┋not┋  ┃
    ┃                 ┗╍╍╍┛  ┃
    ┗━━━━━━━━━━━━━━━━━━━━━━━━┛

The Bar is still borrowed, but it’s not immutable, and so, through UnsafeCell, you can get an &mut reborrow of it — but all the usual &mut rules still apply. UnsafeCell only cancels &’s immutability and not any other borrowing rule.

3 Likes

The point is, this &mut T is not a fresh reference, but a reborrow. Therefore, as long as it is active, all corresponding &UnsafeCell<T> references must be inactive.

So, is ptr::write the only way to implement interior mutability? Can't I generate a &mut T from *mut T, even if I'm certain no other &T or &mut T exists?

I mean if the existence of an &UnsafeCell<T> prevent me from creating a &mut T, then the only way to implement interior mutability is ptr::write.

Confilicts with:

There is no legal way to obtain aliasing &mut , not even with UnsafeCell<T> .

The type &(Foo, UnsafeCell<Bar>) implies the existence of both &Foo and &UnsafeCell<Bar>. While it's understandable that this forbids a &mut UnsafeCell<Bar>, does it also strictly prohibit deriving a &mut Bar from it? Note the &UnsafeCell<Bar> weaks &Bar

It's important to differentiate the types. A &mut UnsafeCell<T> aliasing &UnsafeCell<T> would be problematic, but a &mut T (still besides an &UnsafeCell<T>) is fine (in fact, RefCell and Mutex allow basically exactly that).

I don't think @quinedot's answer was quite correct, or we're both somehow misreading the point @quinedot was making; you can generate a &mut T. In fact, as far as I know, whenever you're allowed do use ptr::write[1] on a raw pointer, you're basically always also allowed to instead create a (short lived) mutable reference from the pointer and do the write through that reference.


  1. and you're also allowed to do a read, i. e. the target is properly initialized, so it can be dropped legally when being replaced through ordinary &mut T-based write operations ↩︎

1 Like

It’s not true that you have to use ptr::write.

  • Today, you can convert that *mut T that you got from UnsafeCell::get() to &mut T, as long as that is compatible with all the ways you are using the UnsafeCell (e.g. there must not be another thread doing the same thing at the same time). This is what Mutex is doing under the hood.
  • In the future (or with nightly rustc), you may be able to use UnsafeCell::as_mut_unchecked() to get from &UnsafeCell<T> to &mut T without needing to touch a raw pointer yourself.
2 Likes

P.S. The first point isn’t specific to UnsafeCell. In general, if you have an *mut T that

  • points to a valid value of type T, and
  • which you could read and write at any time you feel like (within some scope) without that being undefined behavior,

then you may convert that *mut T into an &mut T with an appropriate lifetime and start accessing it safely instead of unsafely.

2 Likes

Reading your reply, I think I misunderstood the question. I took "coexistence of &mut T" to include multiple &mut T.

You can safely convert a &mut T to a &mut UnsafeCell<T> though, so if a &mut T aliasing a &UnsafeCell<T> is fine then so must be a &mut UnsafeCell<T> (though this doesn't mean you can just cast a &UnsafeCell<T> to a &mut UnsafeCell<T>)

Edit:

This is not true since the raw pointer might point to an invalid value (e.g. uninitialized memory). Doing the write through a mutable reference also drops the old value, so it has different semantics.

eh..... Is this implying that &mut T cannot coexist with &UnsafeCell<T>, or that &UnsafeCell<T> can coexist with &mut UnsafeCell<T>?

That detail is why I had added a footnote :slight_smile: I guess it's not entirely proper to put an extra condition in a footnote, but for aliasing concerns it didn't really matter anyway.

Interesting observation. I suppose full actual "aliasing" in the sense that both reference could truly be "used" in an interleaved manner is never allowed. While the &mut T is still active & being used, the &UnsafeCell<T> it was derived from must not be used to crate any new access (doing reads or writes or even just creating new references to T).

I guess you're right then that the types don't make a difference. It's not the type that makes &mut T fine to have “besides” a &UnsafeCell<T> to the same target, but it's the former is derived from the latter what makes things turn out fine. A bit like two &mut T can sort-of "coexist" just fine, in case one is a re-borrows of the other, but with the added benefit that simple handling of the &UnsafeCell<T> reference (moving, copying, passing to functions, etc) will also nor invalidate the &mut T, unlike handling (e.g passing to a function) of a re-borrowed-from &mut T.

2 Likes