I'm currently looking at the 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 they introduce two "marker components" to "tell us which entities are movable and which aren't":
#[derive(Component, Default)]
#[storage(NullStorage)]
pub struct Movable;
#[derive(Component, Default)]
#[storage(NullStorage)]
pub struct Immovable;
Later, we have the following code:
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<_, _>>();
where &entities
is an instance of Entities
, &positions
is an instance of WriteStorage<Position>
, and &movables
and &immovables
are respectively instances of ReadStorage<'a, Movable>
, and ReadStorage<'a, Immovable>
.
As someone neurotic about DRY code, the above two functions really gets my hackles up, and I feel the desire to refactor that away, but I haven't been able to figure out how to compose a function which can handle the different types for &movables
and &immovables
.
For example, if I try this function:
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<_, _>>()
}
and invoke it like:
let mov: HashMap<(u8, u8), Index> = collect(&entities, &movables, &positions);
or like:
let mov: HashMap<(u8, u8), Index> = collect::<Movable>(&entities, &movables, &positions);
... compiling fails with:
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.
For example, if I try this function (as per @AlexeyLarionov's suggestion):
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<_, _>>()
}
compiling fails with:
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.
Do I really need to introduce a trait to make this work? If so, what should that trait look like? And what other changes would I need to make? Will I need to add so much complexity that the cure becomes worse than the disease?
CodePudding user response:
As we discussed in the comments, there are two sources of errors:
- Methods operating on
ReadStorage<T>
orWriteStorage<T>
requireT
to be aComponent
, luckily bothMovable
andImmovable
already were, so to fix it we can simply constraint theT
on this trait. Declaration of the function would look like sofn collect<T: specs::Component> (...)
- Because of copy-pasting, the
.join()
method was called on object(&entities, &storable, &positions)
, whereentities
,storable
,positions
are references already as specified in the function declaration, thus.join()
was called (simply speaking) on type(&&A, &&B, &&C)
, while it's defined for(&A, &B, &C)
. To fix it we need to call(entities, storable, positions).join()
inside thecollect
function
The final version of the code would look like this:
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<_, _>>()
}