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.
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 asT
), this does not change any of the previous caveats.Option<T>
andOption<MaybeUninit<T>>
may still have different sizes, and types containing a field of typeT
may be laid out (and sized) differently than if that field wereMaybeUninit<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.