Home > front end >  In Rust, how can I create a function which will accept a "Marker Component" as a type para
In Rust, how can I create a function which will accept a "Marker Component" as a type para

Time:09-17

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 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.

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 compile rust-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:

  1. Methods operating on ReadStorage<T> or WriteStorage<T> require T to be a Component, luckily both Movable and Immovable already were, so to fix it we can simply constraint the T on this trait. Declaration of the function would look like so fn collect<T: specs::Component> (...)
  2. Because of copy-pasting, the .join() method was called on object (&entities, &storable, &positions), where entities, 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 the collect 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<_, _>>()
}
  • Related