【问题标题】:Store a collection of heterogeneous types with generic type parameters in Rust在 Rust 中存储具有泛型类型参数的异构类型的集合
【发布时间】:2018-01-18 18:25:20
【问题描述】:

我正在尝试在 Rust 中实现一个基本的ECS。我想要一个数据结构,为每个组件存储该特定组件的存储。因为有些组件很常见,有些却很少见,所以我想要不同类型的存储策略,例如VecStorage<T>HashMapStorage<T>

由于游戏引擎的 ECS 不知道组件,我想出了:

trait AnyStorage: Debug {
    fn new() -> Self
    where
        Self: Sized;
}

#[derive(Default, Debug)]
struct StorageMgr {
    storages: HashMap<TypeId, Box<AnyStorage>>,
}

使用 VecStorageHashMapStorage&lt;T&gt; 实现 AnyStorage 特征。由于AnyStorage 不知道T,我添加了两个具体存储实现的另一个特征:ComponentStorage&lt;T&gt;

虽然我能够注册新组件(即在StorageMgrstorages 中添加新的Box&lt;AnyStorage&gt;),但我没有找到插入组件的方法。

这是错误的代码:

pub fn add_component_to_storage<C: Component>(&mut self, component: C) {
    let storage = self.storages.get_mut(&TypeId::of::<C>()).unwrap();
    // storage is of type: &mut Box<AnyStorage + 'static>

    println!("{:?}", storage); // Prints "VecStorage([])"

    storage.insert(component); // This doesn't work

    // This neither:
    // let any_stor: &mut Any = storage;
    // let storage = any_stor.downcast_ref::<ComponentStorage<C>>();
}

我知道我的问题来自storage的类型是&amp;mut Box&lt;AnyStorage&gt;;我可以从中获取具体的VecStorage 吗?

做这一切的重点是我希望组件在内存中是连续的,并且为每种组件类型提供不同的存储空间。我无法解决自己使用Box&lt;Component&gt;,或者我不知道如何使用。

我将我的问题简化为最少的代码on Rust Playground

【问题讨论】:

  • edit 解释为什么它不是How do I create a heterogeneous collection of objects? 的重复项。
  • @Shepmaster 因为“通用”这个词。我知道如何创建异构集合以及如何在 Rust 中使用多态性,但不知道通用性。我应该将此添加为介绍性段落吗?
  • @totorigolo 那是不可能的。 Trait 对象不能有泛型类型参数,因为这些是在编译时解析的。解决这个问题的常用方法是通过所有组件的另一个共同特征来使用/操作包含的对象。或者忘记异质性并使用具体类型(例如,每种类型有多个容器,或者组件使用枚举类型)。
  • @E_net4 感谢您的澄清。但我意识到我的问题的标题非常不清楚。我想要做的不是stricto sensus 异构泛型的集合:从宏观角度来看,这就是StorageMgr。 (我不能说清楚,抱歉)我写了一个共同特征,AnyStorage,正如你所建议的那样,我确信有办法让我的VecStorage 回来,因为println!("{:?}", storage); 打印VecStorage([])
  • 您只能这样做,因为您将Debug 约束添加到AnyStorage。听起来您想在AnyStorage 中使用insert 方法。

标签: rust


【解决方案1】:

我不确定这样的事情是否可行,但我终于想通了。关于您发布的示例失败的原因,有几点需要注意。

  1. 您的示例中的特征AnyStorage 没有实现ComponentStorage&lt;T&gt;,因此因为您将“存储”存储在HashMap&lt;TypeId, Box&lt;AnyStorage&gt;&gt; 中,Rust 不能保证每个存储类型都实现ComponentStorage&lt;T&gt;::insert(),因为它只知道他们是AnyStorages。
  2. 如果您确实将这两个特征组合成一个简单地称为Storage&lt;T&gt; 并将它们存储在HashMap&lt;TypeId, Box&lt;Storage&lt;T&gt;&gt; 中,那么Storage 的每个版本都必须存储相同的类型,因为单个T。 Rust 没有办法根据键的 TypeId 动态键入映射的值,因为这样的解决方案需要这样的解决方案。此外,您不能将T 替换为Any,因为Any 不是SizedVec 和所有其他存储类型都需要它。我猜您知道这一切,这就是您在原始示例中使用两种不同特征的原因。

我最终使用的解决方案将Storage&lt;T&gt;s 存储为Anys 中的HashMap&lt;TypeId, Box&lt;Any&gt;&gt;,然后我将Anys 向下转换为Storage&lt;T&gt;s 在StorageMgr 的实现函数中。我在下面放了一个简短的例子,完整版在 Rust Playground here .

trait Component: Debug + Sized + Any {
    type Storage: Storage<Self>;
}

trait Storage<T: Debug>: Debug + Any {
    fn new() -> Self
    where
        Self: Sized;

    fn insert(&mut self, value: T);
}

struct StorageMgr {
    storages: HashMap<TypeId, Box<Any>>,
}

impl StorageMgr {
    pub fn new() -> Self {
        Self {
            storages: HashMap::new(),
        }
    }

    pub fn get_storage_mut<C: Component>(&mut self) -> &mut <C as Component>::Storage {
        let type_id = TypeId::of::<C>();

        // Add a storage if it doesn't exist yet
        if !self.storages.contains_key(&type_id) {
            let new_storage = <C as Component>::Storage::new();

            self.storages.insert(type_id, Box::new(new_storage));
        }

        // Get the storage for this type
        match self.storages.get_mut(&type_id) {
            Some(probably_storage) => {
                // Turn the Any into the storage for that type
                match probably_storage.downcast_mut::<<C as Component>::Storage>() {
                    Some(storage) => storage,
                    None => unreachable!(), // <- you may want to do something less explosive here
                }
            }
            None => unreachable!(),
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-11
    • 2014-03-11
    • 2012-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多