iterator_item
iterator_item copied to clipboard
An alternative: just use FnMut
This issue is primarily meant to log the semantic alternative I prefer. As it revolves around syntax to make fn() -> impl FnMut() -> Option<T>
rather than fn() -> impl Iterator<Item=T>
, it doesn't quite seem correct to implement it as a macro sketch here.
Full text: https://internals.rust-lang.org/t/rust-generators-exploration-of-potential-syntax/15586/8?u=cad97
It could look like:
fn merge_overlapping_intervals(input: impl Iterator<Interval>) -> impl Iterator<Item = Interval> {
// !1 !2
std::iter::from_fn(move || loop {
// !3
let mut prev = input.next()?;
for i in input {
if prev.overlaps(&i) {
prev = prev.merge(&i);
} else {
yield Some(prev); // !4
prev = i;
}
}
yield Some(prev); // !4
yield None; // !5
});
}
Notes:
- Our semicoroutine just produces
impl FnMut() -> Option<Interval>
. To turn this into an iterator, we useiter::from_fn
. - We wrap the body in a
loop
. This is for convenience: otherwise, the implicit tail expression wouldreturn
a()
, which is notOption<Interval>
, leading to a type error. Asloop
evaluates to type!
, this coerces to our closure's return type just fine. Alternatively, we could have written the last line as a tailNone
. Note thatreturn None;
would not work[4], as we would still have the implicit tail expression returning()
after it. -
?
works here. As our closure returnsOption<Interval>
,?
just works as normal. Ifinput.next()
yields aNone
, we resume from the top of the function the next time the closure is called. - We
yield Some(interval)
here, because this is a-> Option<Interval>
closure.[5] Throughfrom_fn
, this fills theIterator
contract of returning items viaSome(interval)
. - We
yield None
to indicate the end of the iterator stream. If and only ifinput
is fused, we could instead leave this off,loop
ing back to the start of the closure, andinput.next()?
wouldyield None
; however, because we don't requireinput
to be fused, we have to yield it ourselves to ensureNone
is yielded before callingnext
on theinput
iterator again. It also just helps with code clarity to do so explicitly. This could also be written as a tail-returnNone
rather than the closure being a bigloop
(see point 2).
A more limited stabilization would avoid yield closures and instead only allow yield fn:
yield fn merge_overlapping_intervals(input: impl Iterator<Interval>) -> Option<Interval> {
let mut prev = input.next()?;
for i in input {
if prev.overlaps(&i) {
prev = prev.merge(&i);
} else {
yield Some(prev);
prev = i;
}
}
yield Some(prev);
None
}
See the linked irlo post (practically a blog post) for more context into why I think this model is a good model.