I would like to marry for
-loops with generators in such a way that this would be possible. If you consider a Generator
trait with 3 associated types: Yield
, Resume
and Return
, then an Iterator<item=T>
is just a Generator<Yield=T, Resume=(), Return=()>
. We could make Generator
the for
-loop lang item instead of Iterator
and extend for
-loop syntax to allow it to handle these extra type parameters.
For Resume
, we’d just allow continue
to take an argument and have “continue
” with no argument just be sugar for “continue ()
”. A continue
statement at the end of a for
-loop block would be mandatory if the generator needs to resume with anything other than ()
. To demonstrate:
// These three are equivalent
for x in my_iter {
}
for x in my_iter {
continue;
}
for x in my_iter {
continue ();
}
// But we can also have iterators/generators where next/resume takes an argument.
// Here, continue is necessary.
for x in my_generator {
continue 123;
}
For Return
, make the return value of the entire for
expression be the return value of the generator. All iterators currently return ()
when they complete, but we could make RangeFrom
a generator which returns !
. So, in general, we’d allow code that does stuff like this:
let my_generator: impl Generator<Yield=T, Resume=(), Return=String> = ...;
let y: String = for x in my_generator {
...
}
Also, for the hell of it, allow for
-loops to end in a then
clause which can map the generator’s return value.
let my_generator: impl Generator<Yield=u32, Resume=(), Return=String> = ...;
let y: u32 = for x in my_generator {
if x < 123 {
break x;
}
} then z {
u32::parse(z).unwrap()
}
// `z` can be omitted if it has type `()`
let my_generator: impl Generator<Yield=u32, Resume=(), Return=()> = ...;
let y: u32 = for x in my_generator {
if x < 123 {
break x;
}
} then {
456
}
As a final touch, treat the argument to for
as being mutably borrowed inside the main body of the for
-loop, but as having been moved after the for
-loop or within the then
clause. Combined with non-lexical-lifetimes this would allow you to break
or return
with the generator before it’s finished being consumed. eg. this would be legal:
fn take_three<G: Generator<Yield=u32, Resume=(), Return=!>>(g: G) -> (Vec<u32>, Option<G>) {
let mut ret = Vec::new();
for x in g {
ret.push(x);
if ret.len() == 3 {
return (ret, Some(g));
}
}
(ret, None)
}
This would provide a way to use generators without any of those nasty runtime assertions which panic if we call .resume()
on a generator which has already returned.
Edit: I actually have an RFC baking for this but it’s part of a much much larger effects-system RFC which I don’t know if I’ll ever get round to finishing.