在 Rust 中,如何创建一个接受 "Marker Component" 作为类型参数的函数?

In Rust, how can I create a function which will accept a "Marker Component" as a type parameter?

我目前正在研究 Rust Sokoban tutorial, playing with the code as I type it in, to see how I can "improve" it without breaking it. In the chapter on Pushing boxes 他们引入了两个“标记组件”来“告诉我们哪些实体是可移动的,哪些不是”:

#[derive(Component, Default)]
#[storage(NullStorage)]
pub struct Movable;

#[derive(Component, Default)]
#[storage(NullStorage)]
pub struct Immovable;

后来,我们有如下代码:

                let mut mov: HashMap<(u8, u8), Index> = (&entities, &movables, &positions)
                    .join()
                    .map(|t| ((t.2.x, t.2.y), t.0.id()))
                    .collect::<HashMap<_, _>>();
                let mut immov: HashMap<(u8, u8), Index> = (&entities, &immovables, &positions)
                    .join()
                    .map(|t| ((t.2.x, t.2.y), t.0.id()))
                    .collect::<HashMap<_, _>>();

其中&entitiesEntities的实例,&positionsWriteStorage<Position>的实例,&movables&immovables分别是ReadStorage<'a, Movable>ReadStorage<'a, Immovable>.

的实例

作为一个对 DRY code 很神经质的人,上面的两个函数真的让我很烦躁,我很想重构它,但是我一直没能弄清楚如何编写一个可以处理的函数&movables&immovables.

的不同类型

例如,如果我尝试这个函数:

fn collect<T>(entities: &Entities, storable: &ReadStorage<T>, positions: &WriteStorage<Position>)
    -> HashMap<(u8, u8), Index> {
    (&entities, &storable, &positions)
            .join()
            .map(|t| ((t.2.x, t.2.y), t.0.id()))
            .collect::<HashMap<_, _>>()
}

并像这样调用它:

let mov: HashMap<(u8, u8), Index> = collect(&entities, &movables, &positions);

或喜欢:

let mov: HashMap<(u8, u8), Index> = collect::<Movable>(&entities, &movables, &positions);

...编译失败:

error[E0277]: the trait bound T: specs::Component is not satisfied
--> src\resources\input_system.rs:95:46 | 95 | fn collect(entities: &Entities, storable: &ReadStorage, positions: &WriteStorage) | ^^^^^^^^^^^^^^^ the trait specs::Component is not implemented for T | ::: C:\Users\BrianKessler.cargo\registry\src\github.com-1ecc6299db9ec823\specs-0.17.0\src\storage\mod.rs:143:29 | 143 | pub struct MaskedStorage<T: Component> { | --------- required by this bound in MaskedStorage | help: consider restricting type parameter T | 95 | fn collect<T: specs::Component>(entities: &Entities, storable: &ReadStorage, positions: &WriteStorage) | ^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try rustc --explain E0277. error: could not compile rust-sokoban

To learn more, run the command again with --verbose.

例如,如果我尝试这个函数(根据@AlexeyLarionov 的建议):

fn collect<T: specs::Component>(entities: &Entities, storable: &ReadStorage<T>, positions: &WriteStorage<Position>)
    -> HashMap<(u8, u8), Index> {
    (&entities, &storable, &positions)
            .join()
            .map(|t| ((t.2.x, t.2.y), t.0.id()))
            .collect::<HashMap<_, _>>()
}

编译失败:

error[E0599]: the method join exists for tuple (&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, && specs::Storage<'_, Position, FetchMut<'_, MaskedStorage<Position>>>), but its trait bounds were not satisfied --> src\resources\input_system.rs:94:14 | 94 | .join() | ^^^^ method cannot be called on (&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, & &specs::Storage<'_, Position, FetchMut<'_, MaskedStorage<Position>>>) due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: &&specs::Read<'_, EntitiesRes>: specs::Join which is required by (&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, &&specs::Storage<'_ , Position, FetchMut<'_, MaskedStorage<Position>>>): specs::Join &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>: specs::Join which is required by (&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, &&specs::Storage<'_ , Position, FetchMut<'_, MaskedStorage<Position>>>): specs::Join &&specs::Storage<'_, Position, FetchMut<'_, MaskedStorage<Position>>>: specs::Join which is required by (&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, &&specs::Storage<'_ , Position, FetchMut<'_, MaskedStorage<Position>>>): specs::Join

error: aborting due to previous error

For more information about this error, try rustc --explain E0599. error: could not compile rust-sokoban

To learn more, run the command again with --verbose.

我真的需要引入特征才能使这项工作有效吗? 如果是这样,该特征应该是什么样的? 我还需要做出哪些其他改变? 我是否需要增加如此多的复杂性以致于治疗变得比疾病本身更糟?

正如我们在评论中所讨论的,有两个错误来源:

  1. ReadStorage<T>WriteStorage<T> 上运行的方法要求 TComponent,幸运的是 MovableImmovable 已经是,所以要修复它,我们可以简单地将 T 限制在这个特征上。函数的声明看起来像这样 fn collect<T: specs::Component> (...)
  2. 由于复制粘贴,在对象 (&entities, &storable, &positions) 上调用了 .join() 方法,其中 entitiesstorablepositions 已作为引用在函数声明中指定,因此 .join() 在类型 (&&A, &&B, &&C) 上被调用(简单地说),而它是为 (&A, &B, &C) 定义的。要解决这个问题 我们需要在 collect 函数
  3. 中调用 (entities, storable, positions).join()

代码的最终版本如下所示:

fn collect<T: specs::Component>(entities: &Entities, storable: &ReadStorage<T>, positions: &WriteStorage<Position>)
    -> HashMap<(u8, u8), Index> {
    (entities, storable, positions)
            .join()
            .map(|t| ((t.2.x, t.2.y), t.0.id()))
            .collect::<HashMap<_, _>>()
}