After think about this a little while, I think the problem is unsoundness of Cell is the source of the evil. Just think about this, having a &Cell<Option<T>>
is pretty much having a &mut T
, since we can do the following thing:
fn abuse_cell_ref<T>(cell_ref: &Cell<Option<T>>, op: impl FnOnce(&mut T)) {
if let Some(mut inner) = cell_ref.replace(None) {
op(&mut inner);
cell_ref.replace(Some(inner));
}
}
But the evil part is the compiler will treat this reference as normal immutable reference. You don't really have a way to clone a &mut T
, but you definitely can have &Cell<Option<T>>
cloned as it's just a immutable reference. And this makes multiple &mut T
effectively possible in safe Rust.
So there's actually a lot of different way to exploit this. For example, I believe this code is definitely an UB, but it's completely safe code. Even without Pin
, I believe.
In the code above, you are ending up a malformed b-tree data structure.
So my point is Cell
really safe, the problem of Cell is it makes &T not a guarantee of immutability any more. Furthermore, most of other part of the code just believe the contract that everything behind &T is really immutable and this gives malicious code so many ways to break borrowing contract even without unsafe block.