在 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<_, _>>();
其中&entities
是Entities
的实例,&positions
是WriteStorage<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.
我真的需要引入特征才能使这项工作有效吗?
如果是这样,该特征应该是什么样的?
我还需要做出哪些其他改变?
我是否需要增加如此多的复杂性以致于治疗变得比疾病本身更糟?
正如我们在评论中所讨论的,有两个错误来源:
- 在
ReadStorage<T>
或 WriteStorage<T>
上运行的方法要求 T
是 Component
,幸运的是 Movable
和 Immovable
已经是,所以要修复它,我们可以简单地将 T
限制在这个特征上。函数的声明看起来像这样 fn collect<T: specs::Component> (...)
- 由于复制粘贴,在对象
(&entities, &storable, &positions)
上调用了 .join()
方法,其中 entities
、storable
、positions
已作为引用在函数声明中指定,因此 .join()
在类型 (&&A, &&B, &&C)
上被调用(简单地说),而它是为 (&A, &B, &C)
定义的。要解决这个问题
我们需要在 collect
函数 中调用 (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<_, _>>()
}
我目前正在研究 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<_, _>>();
其中&entities
是Entities
的实例,&positions
是WriteStorage<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 traitspecs::Component
is not implemented forT
| ::: 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 inMaskedStorage
| help: consider restricting type parameterT
| 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 compilerust-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 compilerust-sokoban
To learn more, run the command again with --verbose.
我真的需要引入特征才能使这项工作有效吗? 如果是这样,该特征应该是什么样的? 我还需要做出哪些其他改变? 我是否需要增加如此多的复杂性以致于治疗变得比疾病本身更糟?
正如我们在评论中所讨论的,有两个错误来源:
- 在
ReadStorage<T>
或WriteStorage<T>
上运行的方法要求T
是Component
,幸运的是Movable
和Immovable
已经是,所以要修复它,我们可以简单地将T
限制在这个特征上。函数的声明看起来像这样fn collect<T: specs::Component> (...)
- 由于复制粘贴,在对象
(&entities, &storable, &positions)
上调用了.join()
方法,其中entities
、storable
、positions
已作为引用在函数声明中指定,因此.join()
在类型(&&A, &&B, &&C)
上被调用(简单地说),而它是为(&A, &B, &C)
定义的。要解决这个问题 我们需要在collect
函数 中调用
(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<_, _>>()
}