A pattern to avoid using dyn Iterators?

Hey there,

I'm building a tool to lazily apply multiple transformations to a string. Basically I have a Transformer trait that receives a string and returns an Iterator<Item = String>.

i.e. you give it a string and it returns an iterator that when you call next() gives you a lowercase version of the string, then an uppercase one.

I have a struct that receives an unknown number of these Iterators, stores them in a Vec<Box<dyn Iterator<Item = String>>> and does something to them.

However, I wonder if there is a way to avoid so many dynamic dispatch calls to next()

If you actually need to store differently-typed items in a Vec, then there's no way around dynamic dispatch.

If you can instead allow composing statically-typed iterators one-by-one, eg. using a builder-style API, then you can use nested generics instead.

4 Likes

You've only shared english and not code, but if you have something like

trait Transformer {
    type Iter: Iterator<Item = String>
    fn transform(s: String) -> Self::Iter;
}

Then different implementers may have different types and you can't support an arbitrary set of implementors without type erasure (by support I mean, store their iterator ouputs in a Vec).


You could support a known set of implementors with an enum.

If possible, could change the trait to use a concrete type as the iterator instead of an associated type, so that all implementors return the same type.

Sometimes it's possible to move the logic that calls next[1] into the trait so that it doesn't happen on a type-erased object. That's probably not possible here if you're interleaving calls to next on different iterators, but might be possible if all your calls to next for a given iterator are grouped.


  1. or whatever the expensive part is ↩ī¸Ž

3 Likes

Thank you for answering ! It took me a while to figure it out. I really didn't need to keep them in a Vec to begin with and managed to implement it using traits and generics. I was a bit confused at first but it became clearer after I looked at how iterator adapters are implemented.

I'm beginning to grasp how powerful traits and generics can be, this language is really cool.

This is how I implemented it in case anyone's interested:

Btw, it is considered idiomatic to omit type bounds where they aren't directly used. In your playground that's the definition and inherent impl for Transformed.

2 Likes

Thanks !