rust 使用未初始化的数据将`Rc〈RefCell〈MaybeUninit>`克隆为< T>`Rc〈RefCell< T>`是否安全?

n3ipq98p  于 2023-05-07  发布在  其他
关注(0)|答案(1)|浏览(117)

目前在我的项目中,我有一个结构体,看起来像这样:

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()?;

真实的上,Entity结构中有52个EntList字段。
我这样做的原因是因为当实体实际上通过init_entity加载时,实体可以在它们存在之前引用其他实体。例如,EntityA可能引用entity_c列表中的EntityC,实体EntityB可能引用EntityA
这个系统 * 确实有效 *,但我的问题是,当所有实体都被加载时,在任何EntList中都没有None值。因此,为了使这个API更好地使用,我想在解析完成后删除它。我的想法是使用MaybeIninit来代替Ptr结构中的Option
问题是引用可能卸载的实体的每个实体将持有Ref<T>,而不是RefCell<MaybeUninit<T>>
我的问题是,如果RefCell从未被解除引用,那么当数据未初始化时,克隆RefCell<MaybeUninit<T>>并将其转换为RefCell<T>是否安全?
如果它是不安全的,是否可以将所有指针存储为RefCell<MaybeUninit<T>>,然后在加载它们时将它们转换为RefCell<T>
额外信息:没有办法重新排列它们的加载顺序,以避免引用未加载的实体,并且这种结构非常严格,因为它是一种文件格式。
我也一直试图解决这个解决方案与GAT的,但我不认为它会工作,因为MaybeUninit<T> dosent impl Clone,除非T: Copy和有情况下,它不是。但我需要我的实体是`克隆。

pcww981p

pcww981p1#

不允许。

RefCell#[repr(Rust)](默认表示),因此,它的字段顺序是不保证的。即使MaybeUninit<T>保证具有与T相同的布局,这也不适用于包含它的类型。MaybeUninit文档中明确规定了此警告:
虽然MaybeUninit#[repr(transparent)](表示它保证与T相同的大小,对齐和ABI),但这并没有改变任何先前的警告。Option<T>Option<MaybeUninit<T>>仍然可以具有不同的大小,并且包含类型T的字段的类型可以与如果该字段是MaybeUninit<T>时不同地布局(和大小)。
但是,假设组件一旦创建就不可变,则有一种解决方案。
可以使用Cell代替RefCellCell<T> does have the same memory layout as T(尽管这只是目前在beta版和nightly上指定的,但我相信即使在稳定版上也可以依赖它),因此可以将Rc<Cell<MaybeUninit<T>>>转换为Rc<Cell<T>>Rc<T>。因为你只需要replace()(或者实际上是set(),因为你没有使用返回值),这应该对你有用。注意:Rc本身的布局是未指定的,所以你 * 不能 * 将Rc<Cell<MaybeUninit<T>>>转换为Rc<T>,但你 * 可以 * 使用Rc::into_raw()Rc::from_raw(),并在两者之间转换指针。
确保在引用其他组件的组件中存储Rc<T>而不是Rc<Cell<T>>,这样就不能改变它们。

相关问题