iterator_item
iterator_item copied to clipboard
Add generator blocks
This code is to support generator blocks (as seen on zulip!):
type MyIterator = impl Iterator<Item=i32>;
let count = 20;
let my_iter: MyIterator = gen {
for i in 0..count {
yield i;
}
};
The implementation here is a bit invasive because I wanted to work with the existing macro in case someone wants to try any sort of mixed syntax. The general idea is I convert gen { ... }
to gen! { ... }
everywhere before parsing, so that syn is happy parsing the code overall, and a second pass can be made by a visitor.
I'm quite interested in a syntax like this as well.
I'm wondering if we could also have "generator lambdas": gen || { for i in 0..3 { yield i } }
. (Return type optional, and inferred if omitted, as with lambdas.)
I really like the idea of generator blocks. They seem even more useful than generator functions because they can be used, for instance, in argument position as demonstrated in the replace_pairs
test.
I'm quite interested in a syntax like this as well.
I'm wondering if we could also have "generator lambdas":
gen || { for i in 0..3 { yield i } }
. (Return type optional, and inferred if omitted, as with lambdas.)
Generator lambdas are interesting because the argument x
of gen |x| { .. }
could be interpreted in two different ways:
- A construction argument passed in order to create the generator. IMO this doesn't add much value because you might as well capture
x
rather than passing it in explicitly. And if you really do want a lambda which constricts a generator some time in the future, you can always just write|x| gen { .. }
. But it is the most obvious approach and parallels async closures. - A resumption argument passed every time the generator is resumed, a la yield closures or RFC 2936. This is super powerful but runs into all the fun well-worn debates like "what trait should a generator with resume arguments implement". My assertion over in the PATs chat that Iterator is stable and will never add generics makes me wonder if maybe it should actually be generic over resume arguments? Funky. The real problem with this approach is the difficulty of adding similar resume arguments to Stream, since the async context already occupies that role.
@samsartor I would interpret gen |x| { ... }
by interpretation (1); I think interpretation (2) would need substantially different syntax to make it clear.
@joshtriplett Yeah, that take is very common whenever I advocate (2). I personally think there is a close correspondence between coroutines and closures to the extent that sharing the same syntax for both is not just convenient but totally correct. Alas, that is a debate for another day. Hopefully the fact that gen {}
blocks correspond well to async {}
blocks and thus to async closures is enough to work for the time being.
I would probably assume (1) at first, but (2) isn’t shocking either. As you said, what is nice is that we can use gen { … }
and if in the future we want to pass an argument to the resume function, we could add gen |x| { … }
with the meaning of (2).
Is there any semantic difference between a generator block (gen { ... }
) and a generator lambda/yielding closure with no arguments (gen || { ... }
)?
I don't think there would be any difference between gen {}
and gen || {}
.
I don't think there would be any difference between
gen {}
andgen || {}
.
Depends on if you use interpretation (1) or (2):
-
gen || {}
is the same as|| gen {}
: a closure which only returns aimpl Iterator
once called. Whereasgen {}
produces animpl Iterator
directly. This is the same way (unstable) async closures and async blocks work today. -
gen || {}
immediately constructs some sort of generator which takes no resume arguments. This probably implements a different trait but is otherwise likegen {}
.