I have a Board
(a.k.a. &mut Vec<Vec<Cell>>
) which I would like to update while iterating over it. The new value I want to update with is derived from a function which requires a &Vec<Vec<Cell>>
to the collection I'm updating.
I have tried several things:
Use
board.iter_mut().enumerate()
androw.iter_mut().enumerate()
so that I could update thecell
in the innermost loop. Rust does not allow calling thenext_gen
function because it requires a&Vec<Vec<Cell>>
and you cannot have a immutable reference when you already have a mutable reference.Change the
next_gen
function signature to accept a&mut Vec<Vec<Cell>>
. Rust does not allow multiple mutable references to an object.
I'm currently deferring all the updates to a HashMap
and then applying them after I've performed my iteration:
fn step(board: &mut Board) {
let mut cells_to_update: HashMap<(usize, usize), Cell> = HashMap::new();
for (row_index, row) in board.iter().enumerate() {
for (column_index, cell) in row.iter().enumerate() {
let cell_next = next_gen((row_index, column_index), &board);
if *cell != cell_next {
cells_to_update.insert((row_index, column_index), cell_next);
}
}
}
println!("To Update: {:?}", cells_to_update);
for ((row_index, column_index), cell) in cells_to_update {
board[row_index][column_index] = cell;
}
}
Is there a way that I could make this code update the board
"in place", that is, inside the innermost loop while still being able to call next_gen
inside the innermost loop?
Disclaimer:
I'm learning Rust and I know this is not the best way to do this. I'm playing around to see what I can and cannot do. I'm also trying to limit any copying to restrict myself a little bit. As oli_obk - ker mentions, this implementation for Conway's Game of Life is flawed.
This code was intended to gauge a couple of things:
- if this is even possible
- if it is idiomatic Rust
From what I have gathered in the comments, it is possible with std::cell::Cell
. However, using std:cell:Cell
circumvents some of the core Rust principles, which I described as my "dilemma" in the original question.
It entirely depends on your
next_gen
function. Assuming we know nothing about the function except its signature, the easiest way is to use indices:With more information about
next_gen
a different solution might be possible, but it sounds a lot like a cellular automaton to me, and to the best of my knowledge this cannot be done in an iterator-way in Rust without changing the type ofBoard
.You might fear that the indexing solution will be less efficient than an iterator solution, but you should trust LLVM on this. In case your
next_gen
function is in another crate, you should mark it#[inline]
so LLVM can optimize it too (not necessary if everything is in one crate).Not an answer to your question, but to your problem:
Since you are implementing Conway's Game of Life, you cannot do the modification in-place. Imagine the following pattern:
If you update line 2, it will change the
1
in that line to a0
since it has only two1
s in its neighborhood. This will cause the middle1
to see only two1
s instead of the three that were there to begin with. Therefor you always need to either make a copy of the entireBoard
, or, as you did in your code, write all the changes to some other location, and splice them in after going through the entire board.There exists a type specially made for situations such as these. It's coincidentally called
std::cell::Cell
. You're allowed to mutate the contents of aCell
even when it has been immutably borrowed multiple times.Cell
is limited to types that implementCopy
(for others you have to useRefCell
, and if multiple threads are involved then you must use anArc
in combination with somethinng like aMutex
).