I am attempting to implement one iteration of Conway's Game of Life in Rust using the ndarray library.
I thought a 3x3 window looping over the array would be a simple way to count the living neighbours, however I am having trouble doing the actual update.
The array signifies life with #
and the absence of 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 does not compile! The error is within the match
block with "error: cannot borrow as mutable" and "error: cannot assign to immutable index". I attempted for &mut window...
but the library does not implement this(?)
I'm relatively new to Rust and I believe this may be an issue with the implementation of windows by the library. However, I'm not sure and I don't know if there perhaps some variation/fix that allows me to continue with this approach. Do I need to scrap this approach entirely? I'm not sure what the best approach would be here.
Any other suggestions or improvements to the code would be greatly appreciated.
(This code doesn't implement proper rules as I am mutating as I loop and I am ignoring the outer edge, however that is okay in this case. Also, any variations which do these things are also okay - the details are not important.)
Your general approach using ndarray
and windows
is ok, but the problem is that the values that you get from the windows
iterator will always be immutable. You can work around that by wrapping the values in Cell
or RefCell
, which gives you interior mutability. That is, they wrap a value as if it was immutable, but provide an API to let you mutate it anyway.
Here is your code, fairly brutally adapted 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've done above is really just to get your code working, pretty much as-is. But, as E_net4 pointed out, your solution has a major bug because it is mutating as it reads. Also, in terms of best-practices, your usage of String
isn't ideal. An enum
is much better because it's smaller, can be stack-allocated and better captures the invariants of your model. With an enum
you would derive Copy
as below, which would let you use a Cell
instead of RefCell
, which is likely to be better performance because it copies the data, instead of having to count references.
#[derive(Debug, PartialEq, Clone, Copy)]
enum CellState {
Alive,
Dead
}
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