Wrapping Rc around Cell

I’ve these structs:

pub struct Drawable {
    k: DrawableObj,
    x: Cell<f64>,
    y: Cell<f64>,
    opt: Cell<Option<Rc<OptSettings>>>,
}

pub struct OptSettings {
    pub alpha: u8,
    pub scale_x: f64,
    pub scale_y: f64,
    pub rotation_x: f64,
    pub rotation_y: f64,
    pub rotation_z: f64,
}

I can’t do the following code because of course Rc isn’t Copyable, but what then?

impl Drawable {
    pub fn opt(&self) -> Option<Rc<OptSettings>> {
        self.opt.into_inner().clone()
    }

    pub fn set_opt<'a>(&self, opt: &'a OptSettings) {
        self.opt.set(Some(Rc::new(*opt)));
    }
}

You need interior mutability. The typical usage is Rc<RefCell<Option<Settings>> (or Arc<Mutex<…>> for multi-threaded code).

I’d like to not store Settings by default, say, sometimes the Rc pointer will be null and no additional memory will be in heap. Isn’t that possible?

Hmm, tricky. This might work? RefCell<Option<Rc<Settings>>>.

RefCell has to have a room for a flag regardless. Option<Rc<…>> should see that Rc is a non-null pointer and optimize it.

2 Likes

I thought RefCell wouldn’t have a useful method. But it should work because it has a borrow() method and also get_mut(). Well, probably it’ll work when I get to compile.

Note that content of Rc is read-only (that’s why usually refcell is inside it), so with this method you’ll have to replace the Rc when you set options.

I know that we are not talking about hot code, I am just thinking about the problem in general.

I focused mainly on the opt fn, and I came up with two solutions… and I do not like any of them.

First solution

We can use the fact that Option impl Default:

pub fn opt(&self) -> Option<Rc<OptSettings>> {
    let opt = self.opt.take();
    let clone = opt.clone();
    self.opt.set(opt);
    clone
}

I hoped that the optimizer could be able to see that the content of the cell is written two times without being read and throw away some code, but unfortunately it is not the case. I cannot be sure, but I suspect that the runtime check in RefCell is better in this case :slightly_frowning_face:

Second solution

I am not completely sure this is sound (@kornel tell me what do you think), but in any case I don’t like using unsafe. Said that, we can deference the internal pointer and create a clone, which can be returned.

pub fn opt(&self) -> Option<Rc<OptSettings>> {
    unsafe { &*self.opt.as_ptr() }.clone()
}

I tried to reason about soundedness in these terms: there is no function that returns a reference to the internal data, except for get_mut, but this requires object mutability and it is not possible to have both mutable and const reference (from &self). Therefore, it should be not possible to have an alias somewhere else, and the dereference operation is sound. …right? :thinking:

Last observations

I am thriving for Cell::update, which is unstable at to date. Unfortunately, even update won’t be useful in this case with the actual proposal. The main problem with the update(&self, F) where F: FnMut(&mut T) is related to the fact that it is possible to perform a call to update inside the called lambda (because we are not taking a mutable reference to Cell, obviously). In this way it is possible to easily trigger UB, and no one want that.

It looks like that many people really want a safe and sound way of working with Cell<T> where T: !Copy, but it is not easy to come up with a valid abstraction :frowning_face:

1 Like

@RalfJung Soundness query

I am not sure if I understand the question (having a self-contained question would help :slight_smile: ), but is this question essentially a duplicate of https://internals.rust-lang.org/t/impl-t-clone-for-cell-rc-t/5397 ?

3 Likes