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::Componentis not satisfied
--> src\resources\input_system.rs:95:46 | 95 | fn collect(entities: &Entities, storable: &ReadStorage, positions: &WriteStorage) | ^^^^^^^^^^^^^^^ the traitspecs::Componentis 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-sokobanTo 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
joinexists 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::Joinwhich 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::Joinwhich 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::Joinwhich is required by(&&specs::Read<'_, EntitiesRes>, &&specs::Storage<'_, T, Fetch<'_, MaskedStorage<T>>>, &&specs::Storage<'_ , Position, FetchMut<'_, MaskedStorage<Position>>>): specs::Joinerror: aborting due to previous error
For more information about this error, try
rustc --explain E0599. error: could not compilerust-sokobanTo 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?
fn collect<T: specs::Component>, as compiler gracefully suggested? - Alexey Larionovspecs::Jointrait, but from docs it seems it's implemented for objects and for single references. Try passing objects like so(entities, storable, positions).join()...blahblah, eliminating double references - Alexey Larionov&&&&&&&Vec<_>, you'll still be able to call methods of it just fine, similarly&(A, B)can be easily casted to(A, B), while(A, &B)cannot be casted to(A, B), unless you define some cast explicitly - Alexey Larionov