Why is `UnsafeCell<T>` (semantically) invariant?

The reference lists UnsafeCell<T> as invariant.

https://doc.rust-lang.org/reference/subtyping.html#variance

An earlier post in this forum mentions this is a compiler intrinsic.

However it doesn't explain why UnsafeCell<T> is designated as invariant. I can't figure out the exact difference that makes Vec<T> covariant but UnsafeCell<T> invariant. Both look to me as owned container types and the problem resulting in &mut T invariance doesn't seem to reproduce in the case of UnsafeCell<T>. Can I have some examples here that explain the decision of making UnsafeCell<T> invariant wrt T?

Variance in Rust: An intuitive explanation - Ehsan's Blog

I would consider UnsafeCell<T>’s invariance a “hack” to make sure that &UnsafeCell<T> is invariant. (And other similar types like Arc<UnsafeCell<T>>.) The variance system in Rust is simple enough that there isn’t another way to achieve that; and &UnsafeCell<T> must be invariant (in T) for the same reason as why &mut T is invariant (in T).

6 Likes

I don’t like that article. While its main points seems to be accurate, It also claims that the variance issue is the only problem with the code example of MyCell that is given and it implies that using UnsafeCell only serves to fix the variance issue. However the

    fn set(&self, new_value: T) {
        // signature: pub unsafe fn write<T>(dst: *mut T, src: T)
        // Overwrites a memory location with the given value without reading
        // or dropping the old value
        unsafe { std::ptr::write(&self.value as *const T as *mut T, new_value); }
    }

is undefined behavior anyways because you must not mutate through immutable references.

Even their “fixed” version still has this UB in the API (see the last link below).

Can we fix MyCell<T> somehow?

If you find yourself in a situation that need to make your type in-variant, you can include any in-variant type, such as Cell<T> or UnsafeCell<T> in PhantomData, for example with PhantomData<Cell<T>> (Exercise: Can you find other in-variant types?). Checkout how it fixes the issue.


I don’t know if rules for writes through immutable references were less known, or maybe not as clear yet as they are today – but from today’s point of view, this article seems potentially very damaging in teaching a pattern as “safe” that is very clearly UB.

2 Likes

Thanks for point out that UB code. The article is the first non-official tutorial that appears after googling which means many novices have read it including me.

So the real documentation for OP is Subtyping and Variance - The Rustonomicon

UnsafeCell<T> having interior mutability gives it the same variance properties as &mut T

as steffahn said above.

1 Like

Nice. I understand why &UnsafeCell<T> is similar to &mut T in terms of variance.

I found the variance inference algorithm here:

From what I read, the algorithm seeks the most "relaxed" variance that satisfies all constraints. Without a designation in the compiler, UnsafeCell<T> could be inferred as covariant, and hence &UnsafeCell<T>. This is my guess, though.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.