Store iterator in struct (struggle with impl Iterator vs dyn Iterators)

Hi there!

I'm trying to create a struct that has a BTreeMap of ages (Newtype wrapper around u8) to sequences of an enum Severity for a toy infectious disease model.

The underlying source the iterator iterates over is weighted probability distribution (rand::distributions::WeightedIndex) of enum values. I would like to set this up in a way that I can just get the sequence for a certain age value and consume as many (randomly sampled) values as I need.

I got pretty far and I think the function that creates the probabiltiy map now correctly returns a BTreeMap<Age, Box<impl Iterator<...>> but I struggle with how to store this return value in a struct.

The error I get is "expected trait object 'dyn Iterator', found opaque type"

Here is the relevant code:

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Age(pub u8);

#[derive(Copy, Clone)]
enum Severity {
    Asymptomatic,
    Mild,
    Hospitalized,
    ICUHospitalized,
    Dead,
}

const SEVERITIES: [Severity; 5] = [Severity::Asymptomatic, Severity::Mild, Severity::Hospitalized, Severity::ICUHospitalized, Severity::Dead];
const SEVERITY_PROBABILITIES: [u8; 5] = [30, 80, 15, 4, 1];


struct ParameterGenerators {
    severity_distribution_by_age : BTreeMap<Age, Box<(dyn Iterator<Item=Severity>)>>
}

fn createAgeProbabilityMap<R: rand::Rng + ?Sized>() -> BTreeMap<Age, Box<impl Iterator<Item=Severity>>> {
        let mut map = BTreeMap::new();
        let iter = WeightedIndex::new(&SEVERITY_PROBABILITIES).unwrap().sample_iter(rand::thread_rng()).map(|i| SEVERITIES[i]);
        map.insert(Age(0), Box::new(iter));
        map
    }

impl ParameterGenerators {
    pub fn new() -> ParameterGenerators {
        let probability_map = createAgeProbabilityMap();
        ParameterGenerators {
            severity_distribution_by_age : probability_map
        }
    }
}

The full error is:
The error I get is

error[E0308]: mismatched types
   --> fenam-lib\src\lib.rs:116:44
    |
105 | fn createAgeProbabilityMap<R: rand::Rng + ?Sized>() -> BTreeMap<Age, Box<impl Iterator<Item=Severity>>> {
    |                                                                          ---------------------------- the found opaque type
...
116 |             severity_distribution_by_age : probability_map
    |                                            ^^^^^^^^^^^^^^^ expected trait object `dyn core::iter::Iterator`, found opaque type
    |
    = note: expected struct `alloc::collections::BTreeMap<_, alloc::boxed::Box<(dyn core::iter::Iterator<Item = Severity> + 'static)>>`
               found struct `alloc::collections::BTreeMap<_, alloc::boxed::Box<impl core::iter::Iterator>>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `fenam-lib`.

Any help with this would be much appreciated - thanks!
Daniel

The impl Trait syntax does not refer to a specific type, rather it says "Some sort of iterator type goes here, but I'm not telling you which one". When the program is compiled, the compiler figures out which actual type fits in that spot. At runtime the concrete type is known.

On the other hand, the dyn Trait syntax is specific type. It is a special type known as a trait object, which at run time can be any iterator.

Thanks for your answer!

That makes sense - but does that mean that the only way to do this is to give the full concrete type of the actual iterator in the struct? I was hoping that with the indirection of a Box I could simplify this. Also, this way if I make changes to the iterator (e.g. remove the map) then I have to change the type of the struct.

I'm new to rust and maybe with the strict ownership this is how it has to be but I was wondering if there wasn't a way to say "all I know about this member is that it is an Iterator over Severity".

No, you should just use the trait object instead of impl Trait.

Ah, got it now!

If anyone finds this topic in the future with a similar problem: I had to add an explicit type conversion to simplify the Map<...> type in this case. This is the version of createAgeProbabilityMap that worked:

fn createAgeProbabilityMap() -> BTreeMap<Age, Box<dyn Iterator<Item=Severity>>> {
        let mut map = BTreeMap::new();
        let iter = (Box::new(WeightedIndex::new(&SEVERITY_PROBABILITIES).unwrap().sample_iter(rand::thread_rng()).map(|i| SEVERITIES[i]))) as Box<dyn Iterator<Item=Severity>>;
        map.insert(Age(0), iter);
        map
    }

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.