Idiomatic way to re-iterate on a consuming iterator?

I'm currently working with Specs Join iterators

I have situation where I need to loop through a set of "entities" twice, once to establish maximum sizes and then to apply the layout based on the maximums.

The JoinIter that is returned from a (&Storage, &Storage).join() call is using the IntoIter pattern of consuming the data sources. This prevents me from looping the resulting iterator twice, because it's moved the values.

I've solved this by adding a Vec<&mut MyComponent> and filling it in the first loop and then looping over that, but it just seems "fugly".

Is there some better way I'm not seeing here?

My current version's "pseudocode":

            let mut positions: Vec<&mut Position> = Vec::new();

            for (i, (pos, dim, _)) in (positions, dimensions, selectable).join().enumerate() {
                // get starting position + max dimensions for layout purposes
                positions.push(pos); // we can't re-iterate so keep the references
            }

            for (i, pos) in positions.iter_mut().enumerate() {
                // apply layout based on data from previous iteration
            }

If they provide no api to iterate it twice, then there isn't much you can do.

&Storage<T,_> implements Join<Type=&T>. This means you can join by reference as many times as you want without consuming the storage:

for (a, b) in (&storage1, &storage2).join() {}
for (a, b) in (&storage1, &storage2).join() {} // repeat the same join

There's also a corresponding implementation for &mut Storage<T,_> with Item = &mut T.

In your example code, it's not clear what the types of your Join components are. If they are already mutable references, then you might want to join over temporary re-borrows of their contents, something like this:

for (i, (pos, dim, _)) in (&*positions, &*dimensions, &*selectable).join().enumerate() {
    // get layout info
}

for (i, (pos, _, _)) in (positions, dimensions, selectable).join().enumerate() {
    // apply layout
}

You can change the first loop to use mutable borrows like &mut *positions if it needs to mutate any items during that loop. And you can change the second loop to use re-borrowing also, if you want to continue to access the storage after the loop.

1 Like

Thanks that's a good point. I didn't think about re-borrowing here (I rarely do that overall).

They are already references because I send them out in a loop from the main fn run() in the system since I have "actions" pipeline to handle each run (in other words a single run must do multiple actions) so I need to reuse them.