Search code examples
rusttrait-objects

Rust: Trait RangeBounds cannot be made into an object


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.


Solution

  • 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.