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()
and row.iter_mut().enumerate()
so that I could update the cell
in the innermost loop. Rust does not allow calling the next_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;
}
}
Full source
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:
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 is not generally permissible for one thread to modify a Collection while another thread is iterating over it. In general, the results of the iteration are undefined under these circumstances.
Actually, the right way to handle such scenario is to use Iterator to remove the element from the underlying Collection while iterating over it.
This is not permitted in Apex. Look at collections being read only whilst iterating over them, though you can modify existing elements in the collection, but you can't add or remove elements from it. You say I can modify existing elements, I belive that is what I am trying to achieve here.
To answer the first part of your question, you are modifying (adding elements) to the Collection Opptys2, whilst iterating over it. This is not permitted in Apex. Look at collections being read only whilst iterating over them, though you can modify existing elements in the collection, but you can't add or remove elements from it.
Is there a way that I could make this code update the board "in place"?
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 a Cell
even when it has been immutably borrowed multiple times. Cell
is limited to types that implement Copy
(for others you have to use RefCell
, and if multiple threads are involved then you must use an Arc
in combination with somethinng like a Mutex
).
use std::cell::Cell;
fn main() {
let board = vec![Cell::new(0), Cell::new(1), Cell::new(2)];
for a in board.iter() {
for b in board.iter() {
a.set(a.get() + b.get());
}
}
println!("{:?}", board);
}
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:
fn step(board: &mut Board) {
for row_index in 0..board.len() {
for column_index in 0..board[row_index].len() {
let cell_next = next_gen((row_index, column_index), &board);
if board[row_index][column_index] != cell_next {
board[row_index][column_index] = cell_next;
}
}
}
}
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 of Board
.
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:
00000
00100
00100
00100
00000
If you update line 2, it will change the 1
in that line to a 0
since it has only two 1
s in its neighborhood. This will cause the middle 1
to see only two 1
s instead of the three that were there to begin with. Therefor you always need to either make a copy of the entire Board
, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With