Search code examples
rustmaybeuninit

Is it safe to clone an `Rc<RefCell<MaybeUninit<T>>` as `Rc<RefCell<T>` with uninitilized data?


Currently in my project I have a struct that looks something like this:

pub struct Ptr<T>(Rc<RefCell<Option<T>>>);
pub(crate) type EntList<T> = Vec<Ptr<T>>;

struct Entities {
    pub entity_a: EntList<EntityA>,
    pub entity_b: EntList<EntityB>,
    pub entity_c: EntList<EntityC>,
    // ..etc
}

impl Entities {
    pub fn from_rows(locations: &TableSizes) -> Self {
        fn init_vec<T>(size: TableSize) -> EntList<T> {
            let mut v = EntList::with_capacity(size.0);
            for _ in 0..v.capacity() {
                v.push(Ptr::default());
            }
            v
        }
        Self {
            entity_a: init_vec(sizes.entity_a),
            entity_b: init_vec(locations.entity_b),
            entity_c: init_vec(locations.entity_c),
        }
    }

    fn init_entities(&self, entity_reader: &EntityReader) -> Result<()> {
        fn init_entity(uninit_rows: &EntList<E>, entity_reader: EntityReader, entities: &Entities) -> Result<()> {
            for entity in uninit_rows {
                let real_value = entity_reader.read::<T>(entities)?;
                entity.0.replace(Some(real_value));
            }
        }
    }
}
let entities = Entities::from_rows(&table_sizes);
entities.init_entities()?;

In the real thing, there are 52 EntList fields in the Entity struct.

The reason why I have done this is because when then entities are actually loaded via init_entity, entities can reference other entities before they exist. E.g. EntityA might have a reference to an EntityC in the entity_c list, entity EntityB might reference an EntityA.

This system does work, but my issue with it is that when all the entities are loaded, there will be no None value in any of the EntList. So to make the api for this nicer to use, I want to remove it once the parsing is done. My idea is to use a MaybeIninit in place of the Option in the Ptr struct.

The issue is each of the entities that reference the possibly unloaded entity will be holding a Ref<T>, not a RefCell<MaybeUninit<T>>.

My question is, is it safe to make a clone of a RefCell<MaybeUninit<T>> and transmute it to a RefCell<T> when the data is uninitialized if the RefCell is never derefrenced?

If it is unsafe, would it be ok to store all the pointers as RefCell<MaybeUninit<T>> then transmutate them into RefCell<T> once they are all loaded?


Extra info: There is no way to rearrange the order they are loaded to avoid referencing un-loaded entities, and this structure is quite rigid as it is a file format.

I have also been trying to solve this solution with GAT's but I dont think its going to work because MaybeUninit<T> dosent impl Clone unless T: Copy and there are instances where it is not. But I need my entities to be `Clone.


Solution

  • No, this is not allowed.

    RefCell is #[repr(Rust)] (the default representation), and as such, its field order is non-guaranteed. Even though MaybeUninit<T> is guaranteed to have the same layout as T, this does not apply to types containing it. This caveat is specified explicitly in MaybeUninit documentation:

    While MaybeUninit is #[repr(transparent)] (indicating it guarantees the same size, alignment, and ABI as T), this does not change any of the previous caveats. Option<T> and Option<MaybeUninit<T>> may still have different sizes, and types containing a field of type T may be laid out (and sized) differently than if that field were MaybeUninit<T>.

    However, assuming components are immutable once created, there is a solution.

    Instead of RefCell you can use Cell. Cell<T> does have the same memory layout as T (even though this is only currently specified on beta and nightly, I believe it is fine to rely on it even on stable), so converting Rc<Cell<MaybeUninit<T>>> to Rc<Cell<T>> or Rc<T> is possible. Since you only replace() (or actually, set(), since you don't use the return value) this should work for you. Beware: The layout of Rc itself is unspecified, so you cannot transmute Rc<Cell<MaybeUninit<T>>> to Rc<T>, but you can use Rc::into_raw() and Rc::from_raw() and cast the pointer in between.

    Make sure you store Rc<T> and not Rc<Cell<T>> in the components referencing other components so you won't be able to mutate them.