Mutating a road window in Rust ndarray
I am trying to implement one iteration of Conway Game of Life in Rust using ndarray .
I thought that a 3x3 window spanning the array would be an easy way to count live neighbors, however I am having trouble actually updating.
Array means life with #
and no life with
:
let mut world = Array2::<String>::from_elem((10, 10), " ".to_string());
for mut window in world.windows((3, 3)) {
let count_all = window.fold(0, |count, cell| if cell == "#" { count + 1 } else { count });
let count_neighbours = count_all - if window[(1, 1)] == "#" { 1 } else { 0 };
match count_neighbours {
0 | 1 => window[(1, 1)] = " ".to_string(), // Under-population
2 => {}, // Live if alive
3 => window[(1, 1)] = "#".to_string(), // Re-produce
_ => window[(1, 1)] = " ".to_string(), // Over-population
}
}
This code won't compile! The error is inside a block match
with "error: cannot borrow as volatile" and "error: cannot assign an immutable index". I tried for &mut window...
, but the library doesn't implement this (?)
I'm relatively new to Rust, and I believe this can be a problem when the library implements windows. However, I'm not sure and I don't know if there is some change / fix possible that allows me to continue with this approach. Do I need to completely abandon this approach? I'm not sure what the best approach would be here.
Any other suggestions or code improvements are greatly appreciated.
(This code does not implement the correct rules, since I am mutating as a loop I and I am ignoring the outer edge, however in this case everything is fine. Also, any options that do these things are also fine - the details are not important.)
source to share
Your general approach is using ndarray
and windows
is ok, but the problem is that the values you get from the iterator windows
will always be the same. You can get around this by wrapping the values in Cell
or RefCell
, which gives you intrinsic mutability. That is, they wrap the value as if it were immutable, but provide an API for you to mutate it anyway.
Here's your code, pretty brutally tailored to use RefCell
:
use ndarray::Array2;
use std::cell::RefCell;
fn main() {
// creating variables for convenience, so they can be &-referenced
let alive = String::from("#");
let dead = String::from(" ");
let world = Array2::<String>::from_elem((10, 10), " ".to_string());
let world = world.map(RefCell::new);
for mut window in world.windows((3, 3)) {
let count_all = window.fold(0, |count, cell| if *cell.borrow() == &alive { count + 1 } else { count });
let count_neighbours = count_all - if *window[(1, 1)].borrow() == &alive { 1 } else { 0 };
match count_neighbours {
0 | 1 => *window[(1, 1)].borrow_mut() = &dead, // Under-population
2 => {}, // Live if alive
3 => *window[(1, 1)].borrow_mut() = &alive, // Re-produce
_ => *window[(1, 1)].borrow_mut() = &alive, // Over-population
}
}
}
What I did above is really just to get your code to work, pretty much as it is. But as E_net4 pointed out, your solution has a serious error as it mutates as it reads. In addition, in terms of best practice, the use is String
not ideal. enum
much better because it is smaller, can be stacked, and better captures your model's invariants. With help, enum
you would get Copy
as shown below, which would allow you to use Cell
instead RefCell
, which is likely to be more performant since it copies data instead of counting links.
#[derive(Debug, PartialEq, Clone, Copy)]
enum CellState {
Alive,
Dead
}
source to share