Returning iterator of specs components


#1

The specs crate has a Join trait that allows you to iterate over groups of components. The problem is that in order to iterate over components, you need to retrieve the storage for that component in a separate step from actually “joining” the components.

Here’s an example of what I mean. It does not compile.

// Rust 2018 edition
use specs::{World, Component, VecStorage, ReadStorage, Join};
use specs_derive::Component;

#[derive(Debug, Component)]
#[storage(VecStorage)]
pub struct Position {x: i32, y: i32}

#[derive(Debug, Component)]
#[storage(VecStorage)]
pub struct Velocity {x: i32, y: i32}


// Goal: A function with a signature resembling the following:
fn iter_components(world: &World) -> impl Iterator<Item=(&Position, &Velocity)> {
    // Gets the storages of both components and puts them into these two variables on the stack (important!)
    let (positions, velocities) = world.system_data::<(ReadStorage<Position>, ReadStorage<Velocity>)>();

    // Borrows these two variables on the stack
    // Need to do this because Join is only implemented on &ReadStorage<T>
    (&positions, &velocities).join()
} // positions and velocities get dropped here, so this won't compile (they are still borrowed)

This gives you the error:

error[E0515]: cannot return value referencing local variable `velocities`       
  --> src/main.rs:22:5                                                          
   |                                                                            
22 |     (&positions, &velocities).join()                                       
   |     ^^^^^^^^^^^^^-----------^^^^^^^^                                       
   |     |            |                                                         
   |     |            `velocities` is borrowed here                             
   |     returns a value referencing data owned by the current function         
                                                                                
error[E0515]: cannot return value referencing local variable `positions`        
  --> src/main.rs:22:5                                                          
   |                                                                            
22 |     (&positions, &velocities).join()                                       
   |     ^----------^^^^^^^^^^^^^^^^^^^^^                                       
   |     ||                                                                     
   |     |`positions` is borrowed here                                          
   |     returns a value referencing data owned by the current function

I understand exactly why this error is occurring. Is there any way to deal with this given the API that specs provides?

Usually, the solution is to move the values that are being dropped into a struct and return the struct. The problem is that to do that you would have to store both the storages (positions, velocities) and the iterator (&positions, &velocities).join(). If you did that however, you would run into another problem with the fields referring to other fields in the same struct (i.e. a self-referential struct).


#2

This isn’t currently possible because your references are indeed dropped when the function is returned. You’ll need to have an intermediary state that borrows from the world and carries these references outside of the function. Effectively what I’m stating is that you’ll need to create a type that stores the references to the storages relevant to your pattern. Then impl Iterator on that type.

Feel free to check out ecs-disk-manager for some examples of specs being used in practice.


#3

Ah. That’s the answer I was afraid of. :sweat_smile: I figured it wasn’t actually possible. I guess I will need to store the references outside just as you suggested. Thanks!