This question origins from a question I put in user.rust-lang.org: How to count multiple filters of same iterator. To make it clear, it is common that there are multiple adapters and consumers for one iterator:
fn adapter1(iter: impl Iterator<Item=&Elem>) -> impl Iterator<Item=Elem1> {}
fn adapter2(iter: impl Iterator<Item=&Elem>) -> impl Iterator<Item=Elem2> {}
fn consumer1(iter: impl Iterator<Item=&Elem>) -> Vec<Elem3> {}
fn consumer2(iter: impl Iterator<Item=&Elem>) -> Elem4 {}
As they all just need &Elem
, these adapters/consumers SHOULD be able to be driven with the input iter
iterated EXACTLY ONCE (I call this pattern as composable iterator adapter/consumer). However, as answered in the link above, there is NO way to do this without changing the function implementation.
If we can change how function writes, what is the best way to define such composable iterator adapter/consumer? There are two approaches come to my mind:
Approach1: Convert iterator pull-based logic back to for
-loop logic.
There may be a trait roughly like this:
enum ForLoopIteratorOutput<T> {
Pending,
Out(T),
Finished,
}
trait ForLoopIterator<T> {
type Item;
fn on_new_item(item: T) -> ForLoopIteratorOutput<Item>;
}
In this format, we can easily compose these adapters/consumers. However, this requires large rewrite of current implementations.
Approach2: Use async/await logic.
Maybe we could use await
every time we requires a new item from the input iterator, and a custom scheduler to execute the adapters/consumers one-by-one after each item is await
ed. I don't know if this could work, or if this introduces extra runtime overhead.
In my opinion, both approaches are not good enough, and I wonder if we could design a good API for composable iterator adapter/consumer in std or itertools?