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



我目前正在看Rust Sokoban教程,在我输入代码时玩代码,看看我可以如何"改进"。它不会破坏它。在推箱一章中,他们介绍了两种"标记组件"。告诉我们哪些实体是可移动的,哪些是不可移动的:

#[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代码神经质的人,上面的两个函数真的让我很恼火,我想重构它,但是我还没能弄清楚如何编写一个函数来处理&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]:性状绑定T: specs::Component不满足
——>src resources input_system.rs: 95:46| 95 | fn collect(entities: & entities, storeable: &ReadStorage, positions: &WriteStorage)| ^^^^^^^^^^^^^^^特性specs::Component不会在T上实现 users BrianKessler.cargoregistrysrcgithub.com-1ecc6299db9ec823specs-0.17.0srcstoragemod.rs:143:29| 143 | pub struct MaskedStorage{| ---------在MaskedStorage中需要此绑定help:考虑限制类型参数T| 95 | fn collect(entities: & entities, storeable: &ReadStorage, positions: &WriteStorage)| ^^^^^^^^^^^^^^^^^^

错误:aborting due to previous error

有关此错误的更多信息,请尝试rustc --explain E0277。错误:无法编译rust-sokoban

要了解更多信息,请使用——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]:方法join存在于元组(&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, && specs::Storage<'_, Position, FetchMut<'_, MaskedStorage<Position>>>),但它的性状界限不被满足——>src input_system资源。Rs:94:14 | 94 | .join() |无法在(&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, & &specs::Storage<'_, Position, FetchMut<'_, MaskedStorage<Position>>>)上调用^^^^方法由于不满足的trait边界| =注意:下面的trait边界不满足:&&specs::Read<'_, EntitiesRes>: specs::Join哪个是(&&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哪个是(&&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(&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, &&specs::Storage<'_ , Position, FetchMut<'_, MaskedStorage<Position>>>): specs::Join

错误:aborting due to previous error

有关此错误的更多信息,请尝试rustc --explain E0599。错误:无法编译rust-sokoban

要了解更多信息,请使用——verbose再次运行该命令。

我真的需要引入一个特性来让它工作吗?如果是的话,这种特质应该是什么样的呢?我还需要做哪些改变?我是否需要添加如此复杂的内容,以至于治疗变得比疾病本身更糟糕?

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

  1. ReadStorage<T>WriteStorage<T>上操作的方法要求TComponent,幸运的是MovableImmovable已经是,所以要修复它,我们可以简单地约束T在这个特性上。函数的声明如下:fn collect<T: specs::Component> (...)
  2. 由于复制粘贴,在对象(&entities, &storable, &positions)上调用.join()方法,其中entities,storable,positions是已经在函数声明中指定的引用,因此.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<_, _>>()
}

最新更新