Nested loops - beat the borrow checker
When coming to rust from other languages, the borrow checker can be frustrating. Common, ingrained ways of doing things just don’t work, and you have to find the “idiomatic rust” way. I guess this is a good thing, and helps prevent the “all my python code looks like bash scripts” problem, whereby you take your patterns for one language and shoehorn them into whatever language you are actually using, but it doesn’t stop it being frustrating…
One pattern I get into quite a bit is the nested loop, where you loop over a collection, and within that loop, loop over it (or a subset) again.
Here is a trivial example you might see in a turn based game. Each player takes their turn (outer loop). When a player acts, the other players see the move, so get a call with the move the other player just made (inner loop). The simple implementation in rust doesn’t compile:
Common code:
Broken loop:
There are several ways around this:
- Iterate over a range for the outer loop.
- Restructure
- Use interior mutability
Let’s look at each of these in turn.
1) Iterate over a range for the outer loop
We can change the outer loop to iterate over a range representing the indices into the Players Vec, and just get a mutable reference to a player for the small scope in which it is needed.
This is the easiest way out here - we don’t need to create a mutable iterator at the top level. If it wasn’t for the inner loop, a mutable iterator would be a bit less code, but with nested loops, itersting over a range allows us to only borrow each player mutably for the short time we are mutating it. When we run the second loop, we have nothing borrowed, so can create a mutable iterator.
2) Restructure
Many times, a nested loop can be replaced with a different program structure. In this case, instead of calling each player to tell it about each event from another player, we can create a game state which is visible to each player.
In our trivial game simulation, the players don’t need to know the moves of the other players until it is thier turn to act. This allows us to refactor the player trait to take in the list of moves as an argument to act.
This negates the need to have an inner loop.
3. Use interior mutability
Interior mutability moves the borrow checking from compile time to runtime. When we know we won’t have multiple mutable borrows at the same time, but are not able to prove it to the compiler, we can use this to get our program to work. We don’t need to do this here, because as the examples above show, the desired goal can be achieved without it. The rust book has a better example for the use of RefCell with the MockMessenger
For reference, here is an example using RefCell to achieve this.
We change the SimplePlayer to hold a *RefCell<Vec
Now the original loop works as expected. Here is the full code:
Thanks for reading. If you have any questions, suggestions, or to point out any mistakes, please contact me at the email address below. I’d love to hear from you.