我正在为一个对象存储开发一个API,我正在努力找到一种在Rust中实现它的方法.这有点令人沮丧,因为我对如何在C++中实现这一点有一个很好的想法,所以可能这个设计从根本上不适合Rust ,但我希望这里的人们能够提供一些有用的见解.

我希望能够做到以下几点:

// This is clone & copy
struct Id<Identifiable> {
  // What I think would be needed to get this entire system 
  // to work, but it is not fixed like this so alternate 
  // ideas are welcome.
  raw_id: usize,
  _marker: PhantomData<Identifiable>,
}

struct Store {
  // ... don't know how to implement this
}

trait Object {
  // ... some behavior
}

struct ObjectA {}
impl Object for ObjectA {}

struct ObjectB {}
impl Object for ObjectB {}

impl Store {
  pub fn create_object_a(&mut self) -> Id<ObjectA> { todo!() }
  pub fn create_object_b(&mut self) -> Id<ObjectB> { todo!() }

  pub fn get<'a, Obj: Object(&self, id: Id<Obj>) -> &'a Obj { todo!() }
}

其用途如下:

let store = Store::new();
let handle_a = store.create_object_a();
let handle_b = store.create_object_b();

let obj_a = store.get(handle_a); // obj_a is of type &ObjectA
let obj_b = store.get(handle_b); // obj_b is of type &ObjectB

由于可以放入存储区的具体类型是静态已知的(只有通过create_something()方法构建时,它们才能在存储区中),我觉得类型系统中应该有足够的信息来实现这一点.我最想避免的一件事是Vec<Box<dyn Any>>,因为它引入了额外的间接性.

我意识到这在安全防锈中是不可能的,尽管我觉得使用不安全防锈应该是可能的.我的想法是,它在某种程度上类似于Bevy ECS实现存储组件的方式(相同类型的组件连续存储在内存中,我也想在这里看到这一特性),尽管我很难确切地理解它是如何工作的.

希望有人对如何实现这一点有想法,或者有一个更好的替代设计.非常感谢.

推荐答案

您可以创建一个关联的类型和特征,指定如何从存储中检索适当类型的Vec,并将实现该特征的类型设置为Object特征的关联类型:

trait StoreGet<T> {
    fn raw_storage(store: &Store) -> &[T];
}

trait Object: Sized {
    type StoreGet: StoreGet<Self>;
    // more behavior
}

由于特征是泛型的,它的行为就好像每种类型都有单独的特征.因此,您可以使用单一类型,并实现所需类型的特性.例如:

// Your store type:
#[derive(Default)]
struct Store {
    a_store: Vec<ObjectA>,
    b_store: Vec<ObjectB>,
}

// A type that knows how to retrieve `&[ObjectA]` and `&[ObjectB]`
// from Store:
struct ObjectStoreGet;
impl StoreGet<ObjectA> for ObjectStoreGet {
    fn raw_storage(store: &Store) -> &[ObjectA] {
        &store.a_store
    }
}
impl StoreGet<ObjectB> for ObjectStoreGet {
    fn raw_storage(store: &Store) -> &[ObjectB] {
        &store.b_store
    }
}

// ObjectA and ObjectB name that type as StoreGet:
struct ObjectA {}
impl Object for ObjectA {
    type StoreGet = ObjectStoreGet;
}

struct ObjectB {}
impl Object for ObjectB {
    type StoreGet = ObjectStoreGet;
}

最后,store 实现如下所示:

impl Store {
    pub fn create_object_a(&mut self) -> Id<ObjectA> {
        let raw_id = self.a_store.len();
        self.a_store.push(ObjectA {});
        Id {
            raw_id,
            _marker: PhantomData,
        }
    }
    pub fn create_object_b(&mut self) -> Id<ObjectB> {
        // ... much the same as create_object_a ...
    }

    pub fn get<Obj: Object>(&self, id: Id<Obj>) -> &Obj {
        &Obj::StoreGet::raw_storage(self)[id.raw_id]
    }
}

Playground

请注意,您应该查看an arena crate,而不是使用自己的Id+Vec(尽管如果您自己实现它,这并不是一个悲剧——重用它只是一个好主意).

Rust相关问答推荐

rust - 如何在没有0填充的情况下解析带有日期和月份的日期字符串?

ManuallyDrop ABI 是否保证为 repr(透明)?

如何让函数返回具有相同项目类型的两个迭代器之一?

如何创建一次性可变引用?

我应该如何描述泛型类型的生命周期关系?

模式匹配引用时的奇怪类型

rust clap 解析 ipv4Addr

如何将字符串拆分为第一个字符和其余字符?

为什么 BTreeMap 是可散列的,而不是 HashMap?

Rust中切片(slices)和引用(references)之间是什么关系

从引用(reference)创建切片(slice)

在稳定的 Rust 中模拟 BTreeMap::pop_last

具有任意常量表达式的 cfg 属性

什么是borrowed?

为什么直接传递函数时会出现“type mismatch”错误,但它与等效闭包一起使用?

无法在用户定义类型的“Vec”中添加分配

为什么这个borrow仍然“ active”?

为什么我可以在 Rust 中从向量末尾开始切片?

如何修复字符串字段不实现“copy”?

具有内部可变性的单元格允许任意mutation action