Thinking more about this, it seems to me that instead of a traditional internal iterator, what’s really wanted here is the dual of a generator, i.e. some sort of “sink”.
With flexible enough generators, one could write yield
to suspend and receive the next value when the generator is resumed.
The take
example could look like this:
trait Iterator {
fn sink_into<G, R>(&mut self, mut g: G) -> R
where G: Generator<Option<Self::Item>, Yield=(), Return=R>
{
loop {
match g.resume(self.next()) {
Yield(()) => {},
Return(x) => return x
}
}
}
fn try_fold<F, T: Try>(self, init: T::Ok, mut f: F) -> T
where Self: Sized, F: FnMut(T::Ok, Self::Item) -> T,
{
let mut accum = init;
self.sink_into(|| {
while let Some(next) = yield {
accum = f(accum, next)?;
}
Try::from_ok(accum)
})
}
}
struct Take<I> {
n: usize,
iter: I,
}
impl<I> Iterator for Take<I> where I: Iterator {
fn sink_into<G, R>(&mut self, mut g: G) -> R
where G: Generator<Option<Self::Item>, Yield=(), Return=R>
{
if self.n == 0 {
return iter::empty().sink_into(g);
}
let n = &mut self.n;
self.iter.sink_into(move || {
*n -= 1;
loop {
match g.resume(yield) {
Yield(()) => {
if *n == 0 {
return iter::empty().sink_into(g);
}
}
Return(x) => return x
}
}
})
}
}
Moreover, I’m now wondering if generators could automatically be used to generate code like this, i.e. transform yield x
to g.resume(x)
, alongside the next
method, from the same source, i.e. you’d only write:
fn take(self, mut n: usize) -> impl Iterator<Item=Self::Item> {
if n == 0 {
return;
}
for x in self {
n -= 1;
yield x;
if n == 0 {
break;
}
}
}