When to use UnsafeCell

Hello,

I found that std library is perfer to use UnsafeCell very much, but I can't figure out why.

For example:

In std library, upgrade is wrapped in UnsafeCell:


struct Packet {

upgrade: UnsafeCell<Myupgrade>,

}

pub fn send(&self, t: T) {

…

ptr::write(self.upgrade.get(), *SendUsed);

…

}

and why not just make upgrade enum directly like this:


struct Packet {

upgrade: Myupgrade,

}

pub fn send(&mut self, t: T) {

…

self.upgrade = SendUsed,

…

}

In this case it is because UnsafeCell lets you modify upgrade without send taking a &mut self.

Hello, alice

then my question is why "&mut self" is not the prefred solution?

(or can "&mut self" and "&self + UnsafeCell" be substituted for one another in any situation?)

The main point is that &mut T is not just mutable reference, it's - and this is, in fact, more important - an exclusive reference, i.e. the reference which guarantees that the referenced object is not accessed through any other means. If the object must be both shared and mutable, you can't use &mut T and must either:

  • wrap it into the UnsafeCell and provide your own means to make sure there's no data races accessing it;
  • or - more preferably - use some safe abstraction like Mutex (in multithreaded case) or Cell/RefCell (in single thread).
1 Like

The authors wanted the channel to take a &self method, because such methods are much more convenient to call. An &mut self reference requires you to prove to the compiler that you have exclusive access to the element, but &self does not.

As for when you can use UnsafeCell, well you are not allowed to modify a value from multiple threads at the same time, so the surrounding API has to somehow make that impossible to happen. In the case of an mpsc channel, this is enforced because the type is not Sync. Another solution to this problem is a Mutex, which uses a lock to have threads wait for each other, ensuring that only one of them can access it at the time.

There are also non-thread related problems with UnsafeCell. E.g. consider if Vec had a &self clear method:

let vec = vec![1, 2, 3];
let ref_1 = &vec[1];
vec.clear();
println!("{}", ref_1);

Normally this is prevented by the compiler because clear takes &mut self, but if clear only requires an immutable reference, the compiler will allow the above code even though it lets you use a value after it is destroyed.

Any use of UnsafeCell has to make sure either kind of problem cannot happen.

1 Like

I have an article on this subject:

The article discusses the standard library Cell type, which internally uses an UnsafeCell, however a Cell restricts the usage in various ways to avoid all of the above problems.

1 Like

Thank you Alice, this really a big help!