UnsafeCell behavior details

After this thought-provoking post I started to look deeper into Rust memory model and ended up with two questions about UnsafeCell.

I) The documentation is not totally clear to me. One one hand crate-level documentation clearly states

When UnsafeCell is immutably aliased, it is still safe to obtain a mutable reference to its interior and/or to mutate it.

One the other hand get() function documentation states

This can be cast to a pointer of any kind. Ensure that the access is unique when casting to &mut T,

I read it as "Ensure that the write access is unique when casting to &mut T", is my understanding correct?

II) Both crate level and function level documentation agree that

However, it is up to the abstraction designer to ensure that no two mutable references obtained this way are active at the same time, and that there are no active mutable references or mutations when an immutable reference is obtained from the cell.

ensure that there are no mutations or mutable aliases going on when casting to &T

My question is here is why the order important, how an immutable reference obtained after creating a mutable reference are "worse" than an immutable reference obtained before the mutable? I strongly suspect it related to instruction reordering and cache coherence, but I was not able to construct an example.

Thank you for help!

1 Like

Part 1 is basically saying it’s ok to have multiple &UnsafeCell references outstanding so long as when grabbing a &mut T from the interior of one of those cells it’s unique.

Part 2 is saying essentially the same thing but phrased a bit differently. The point all those statements are really trying to drive home is that you can either have multiple immutable references (ie shared aliasing) or you can have a single mutable alias - it’s an xor condition.

If you obtain a &T from the cell, the compiler assumes nothing can modify the value while the borrow is active. This can allow it to make optimizations, such as hoisting reads from memory into registers. Clearly that would break code if a mutation was possible in the meantime. Similarly, if you hold a &mut the compiler assumes you have a unique alias to the value. For example, that may allow you to send the mutable reference to another thread, which can mutate the value without any synchronization. Meanwhile, if you have an immutable reference by mistake on the other thread, you will get a data race.

But the main takeaway is “many shared aliases XOR a single unique alias”, where unique = mutable.

3 Likes

This rule is very natural from code reordering point of view. I still feel documentation is a bit vague, but I think I grabbed the idea. Could you please have a look at Rust Playground and comment whether my understanding is correct? I tried to cover all the essential scenarios , three of them valid, and three of them not. Thank you!

Yup, that looks right to me.

As for docs, if you have suggestions for improvement I’m sure the Rust docs team will be happy to field them.

2 Likes

Thanks for your help! I'll try to brush up my technical writing skills :slight_smile:

For those stumbling upon this thread: Rust memory model is being discussed and formalized here . Depending on the outcome, it might turn out that simply creating an aliased reference is OK, but actually using it is UB. In any case, here be dragons.

Truth be told, that's pretty much how I think of it already. As discussed a bit in Learning Rust with Entirely Too Many Linked Lists - #19 by vitalyd, it's already the case that you can have multiple aliases "resident in memory" - they just cannot be "active" (i.e. read/written) at the same time.

But the above gets into the whole business of "can a &mut be considered a token of having a unique access", even if you don't read/write through it. As you say: