Search code examples
rustdispatch

How to implement iter method using static/dynamic dispatch?


I need to implement the method iter, which returns something which implements the trait Iterator<Item = char>. But the return value will be different implementations, depending on the enum variant.

Something like this:

pub enum Class {
    SingleChar(char),
    Range(Range),
    And(Vec<Class>),
    Or(Vec<Class>),
}

impl Class {
    pub fn iter(&self) -> Iterator<Item = char> {
        match *self {
            Class::SingleChar(c) => vec![c],
            Class::Range(ref range) => range.iter(),
            Class::And(ref classes) => {
                let iter: Option<_> = classes.iter().fold(None, |iter, &class| {
                    match iter {
                        None => Some(class.iter()),
                        Some(iter) => Some(iter.merge(class.iter())),
                    }
                });
                Box::new(iter.unwrap())
            },
            Class::Or(ref classes) => {
                let iter: Option<_> = classes.iter().fold(None, |iter, &class| {
                    match iter {
                        None => Some(class.iter()),
                        Some(iter) => Some(iter.interleave(class.iter())),
                    }
                });
                Box::new(iter.unwrap())
            },
        }
    }
}

range.iter() returns a struct that implements Iterator<Item=char>.

merge and interleave are itertools methods, which return MergeAscend and Interleave respectively (both of them implement Iterator<Item=char>)

  1. How to implement such a scheme using static dispatch?
  2. If static dispatch is not possible, how to implement such a scheme using dynamic dispatch?

Solution

  • It is not possible to do it using static dispatch. There is a tracking RFC issue on unboxed abstract return types, but Rust is not there yet (and I'm not sure if it could cover the use case of returning different types). Therefore, dynamic dispatch is the way to go.

    You're pretty close, actually. Just make the return type Box<Iterator<Item=char>> and add more boxing:

    pub fn iter(&self) -> Box<Iterator<Item=char>> {
        match *self {
            Class::SingleChar(c) => Box::new(Some(c).into_iter()),
            Class::Range(ref range) => Box::new(range.iter()),
            Class::And(ref classes) => {
                let iter: Option<_> = classes.iter().fold(None, |iter, &class| {
                    match iter {
                        None => Some(Box::new(class.iter())),
                        Some(iter) => Some(Box::new(iter.merge(class.iter()))),
                    }
                });
                iter.unwrap()
            },
            Class::Or(ref classes) => {
                let iter: Option<_> = classes.iter().fold(None, |iter, &class| {
                    match iter {
                        None => Some(Box::new(class.iter())),
                        Some(iter) => Some(Box::new(iter.interleave(class.iter()))),
                    }
                });
                iter.unwrap()
            },
        }
    }
    

    This should work.