I sumbled upon a problem while trying out Rust, that might point to a bigger non understanding of its concepts on my part.
My goal is to programm a variant of Conway's Game of Life. I want the values when cells are created or stay alive not to be hard coded, but within a struct. My first attempt was to create a struct
use std::ops::Range;
struct Rules {
be_born: Range<usize>,
stay_alive: Range<usize>,
}
impl Rules {
pub fn new(be_born: Range<usize>, stay_alive: Range<usize>) -> Rules {
Rules { be_born, stay_alive }
}
}
let rules = Rules::new(2..4, 3..6);
This object is later used within the algorithm that iterates over all the cells. It works fine, until I also want to allow other kind of Ranges during creation like for example RangeTo (2..=3).
I know I could rewrite the struct Rules
to be generic.
use std::ops::RangeBounds;
struct Rules<BR: RangeBounds<usize>, AR: RangeBounds<usize>> {
be_born: BR,
stay_alive: AR,
}
This in turn would force me to make all the algorithms I use to be generic as well. This seems to be quite a lot of overhead just to include two simple ranges.
On the other hand none of my attempts to include variables of type RangeBounds
directly into my struct succeeded. I tried &dyn RangeBounds<usize>
or Box<&dyn RangeBounds<usize>>
, just to always get the error E0038 that I cannot make this trait into an object.
Is there any other way to get this done, or is there some other feasable way I do not see?
Thank you in advance for all your hints.
For a broad answer, we need to know how exactly you are iterating over be_born
and stay_alive
fields. But for solving the problem you point out that you want to use different kind of ranges, the easiest way is specifying that be_born
and stay_alive
fields are both an Iterable
that returns usize
when iterated over, aka Iterator<Item=usize>
:
struct Rules<T, U>
where
T: Iterator<Item=usize>,
U: Iterator<Item=usize>
{
be_born: T,
stay_alive: U,
}
impl<T, U> Rules<T, U>
where
T: Iterator<Item=usize>,
U: Iterator<Item=usize>
{
pub fn new(be_born: T, stay_alive: U) -> Self {
Self { be_born, stay_alive }
}
}
fn main() {
let rule = Rules::new(2..4, 3..6);
let other_rule = Rules::new(2..=4, 3..=6);
let another_rule = Rules::new(2..=4, 3..6);
for x in rule.be_born {
println!("born: {}", x);
}
for y in rule.stay_alive {
println!("alive: {}", y);
}
}
This will also allow you to assign non-range but iterable types to be_born
and stay_alive
fields. If you want to restrict yourself to range types only, you can replace every Iterator<Item=usize>
in the code with Iterator<Item=usize> + RangeBounds<usize>
, which means "a type which both implements Iterator<Item=usize>
and RangeBounds<usize>
".
For further usage of Iterator
trait, see the book.