ais523
ais523

Reputation: 998

Could `Cell` in Rust be safely used on `Rc` specifically?

It's been established (e.g. in this question) that Rust's Cell is not usable for non-Copy types, because the .get() operation is only safe if it can guarantee that the process of cloning/copying the data out of the Cell is not going to be interrupted by something that assigns to the Cell.

The current restriction in Rust, therefore, is that Cell.get() is usable only if the type inside the cell is Copy; Clone is not enough, because there can't be any guarantee that the .clone() call doesn't somehow manage to access the Cell being cloned from and write to it while it's in the middle of being read. So code like this is illegal, because the compiler doesn't know for certain that CloneableObject.clone() doesn't do something absurd:

use std::cell::Cell;
#[derive(Debug, Clone)]
struct CloneableObject {}

fn main() {
    let x = Cell::new(CloneableObject {});
    println!("{:?}", x.get());
}

My question is about the special case where the type wrapped by the Cell is the standard library type Rc. Rc is special in that Rc.clone() doesn't actually do any copying, and doesn't inspect the value that the Rc points to; it just increments an integer inside the Rc, something that can't possibly write to a Cell that contains an Rc.

Presumably, this would imply that code like the following could safely be legal:

use std::cell::Cell;
use std::rc::Rc;

fn main() {
    let x = Cell::new(Rc::new(0i32));
    println!("{:?}", x.get());
}

However, it doesn't compile at present, because there isn't a special case for Cell together with Rc.

My question is: Is there any safety/soundness reason not to add such a special case for Cell<Rc<T>>? Or is this simply a case of "this would be perfectly safe, but we haven't taught the compiler how to recognise that it's safe yet"? (If doing this would be safe, I may submit it as a feature suggestion – it would be more efficient than RefCell<Rc<T>> due to not needing runtime checks – but want to make sure that there isn't some unsoundness that I'm missing.)

To avoid any confusion: I am intentionally asking about Cell<Rc<T>> in this question, not the more commonly used Rc<Cell<T>>/Rc<RefCell<T>>.

Upvotes: 2

Views: 644

Answers (1)

kmdreko
kmdreko

Reputation: 60492

Yes, it probably could be, but in its current state Rust lacks the specialization features needed to indicate that.

In theory, there could be a trait to indicate that a type is safe to clone from Cell::get:

use std::cell::UnsafeCell;
use std::rc::Rc;

pub struct Cell<T> {
    inner: UnsafeCell<T>,
}

impl<T> Cell<T> {
    pub fn set(&self, value: T) {
        unsafe { *self.inner.get() = value };
    }

    pub fn get(&self) -> T
    where
        T: CellCloneable,
    {
        unsafe { *self.inner.get() }.clone()
    }
}

trait CellCloneable: Clone {}
impl<T> CellCloneable for T where T: Copy {}
impl<T> CellCloneable for Rc<T> {}

Unfortunately, this also doesn't compile also due to lacking specialization features.

And since the compiler cannot guarantee that an implementer doesn't end up calling Cell::set on itself, it'd have to be an unsafe trait. This trait would be very easy to get wrong: any dynamic dispatch would make this safety constraint impossible to guarantee and unrelated code could be updated to violate this constraint without using any unsafe itself.

IMO, this addition would go against Cell's goal of being a fairly basic and safe primitive for interior mutability.

Upvotes: 1

Related Questions