-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add gen
blocks and reserve keyword in Rust 2024
#3513
Conversation
…eferential` `gen` block issues
* minor typos * intro * motivation * guide * reference * implementation * alternates * rationale * future * whitespace
One thing worth mentioning that I mentioned in the I personally think it would be a good idea to not limit this syntax to just iterators, instead allowing arbitrary generators, but special-casing to iterators when the return value is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to mention iterator_item
which is based on propane and accepts "flexible" syntax. It allows anyone using it to mix and match with the alternatives already mentioned in this document, and it also has some care taken to making the diagnostics make sense, even without additional compiler support, so rustc could provide an even better experience.
(NOT A CONTRIBUTION) I'm very enthusiastic about adding something like this to the language, and there's nothing in this RFC I don't like. I don't have a strong opinion about what keyword is chosen for this feature. In my own head, I think of the general purpose mechanism (called generators in rustc) as coroutines and use the term "generator" just for functions that return iterators. In other words generator is to Iterator as async is to Future. I also want to highlight a quote from the original proposal to use external Iterators as the definition of Iterators in Rust, way back in 2013:
The way it turned out, Rust adopted this sort of thing for futures long before it adopted it for iterators. But an RFC like this was always the plan! |
There were some places with erroneous or unclear punctuation and capitalization. Let's fix those and make some related typographic and linguistic improvements.
Based on a DM with boats and inspired by the way borrowck handles non-self-referential coroutines, there does seem to be a way to express leasing coroutines without typestate, though it requires its own extension to the type system. In a movable coroutine, borrowck treats In a leasing coroutine, If we had a way to quantify over places, we could incorporate those places into a single type to give the step parameter. For example (wildly inventing syntax): for<place waker, place context> |cx: &'{context} mut Context<'{waker}>| {
.. cx = yield ..
} To get a leasing coroutine, something also needs to denote the effect of This all gives unnecessarily complicated types, and presumably nobody wants to write this all down for their custom coroutines. Instead, we could leave this all out of the surface syntax, and introduce a new kind of lifetime elision specifically for coroutine step parameters: quantify over a place for every implicit lifetime, and infer which ones The resulting implementation of the Notably I don't think any of this is really tied to the syntax that treats the step argument as the result of |
We had already opened a tracking issue for this work, so let's fill that in here.
In addition to giving the file the correct number, let's call this `gen-blocks` rather than `gen-fn` since we removed `gen fn` from the main body of this RFC.
The feature name in the draft was a placeholder. Let's update this to the actual feature name now in use.
We had a mix between hard wrapped lines of various widths and unwrapped lines. Let's unwrap all lines.
The main body of the RFC discusses how we might implement `FusedIterator` for the iterators produced by `gen` blocks, but this was not listed as a future possibility. Let's do that.
There was a statement in the draft about, as a downside, something needing to be pinned for the entire iteration rather than just for each call to `next`. But, of course, under the pinning guarantees, these are equivalent. Once something is pinned, unless it is `Unpin`, it must be treated as pinned until it is destructed. Let's remove this statement.
In RFC 3101 we reserved in Rust 2021 prefixed identifiers such as `prefix#ident`. For this reason, we can make `gen` blocks available in Rust 2021 using `k#gen` as was anticipated in the (currently pending) RFC 3098. It's less clear what to do about Rust 2015 and Rust 2018, however, so let's mark this as an open question. (Thanks to tmandry for raising this point.)
We had been meaning to do some final copyediting prior to this RFC being merged, so let's do that. In addition to making the text a bit more regular and precise, fixing some minor errors, removing outdated information, and adding references between sections, we've tried to "tighten it up" a bit where possible. We've been careful to not change anything of semantic significance or otherwise of significance to the consensus.
The Koka language provides an interesting alternative data point for how generators and other powerful control flow constructs could work in a typed language such as Rust. Let's include an example in the prior art section. (Thanks to zesterer for asking for this.)
Using the no-op `Waker`, we can express generators and coroutines in Rust. Let's close our list of prior art examples with that.
Under this RFC, it's possible to yield one last value concisely with `return yield EXPR`. Let's make a note of that. (Thanks to Nemo157 for pointing this out and to pnkfelix for suggesting that this be noted in the RFC.)
The motivating example we had given for `gen` blocks admitted too easy an implementation with existing stable iterator combinators. Let's make the example more *motivating* by showing a simple algorithm, run-length encoding, that's more difficult to implement in other ways. (Thanks to Ralf Jung for pointing out the need for a better example.)
gen
keyword in 2024 edition for Iterator
generators gen
blocks and reserve gen
keyword in Rust 2024
gen
blocks and reserve gen
keyword in Rust 2024gen
blocks and reserve keyword in Rust 2024
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just leaving an informative about how Kotlin did coroutines so it can be better explained.
[ruby-enumerator]: https://ruby-doc.org/3.2.2/Enumerator.html | ||
[ruby-fiber]: https://ruby-doc.org/3.2.2/Fiber.html | ||
|
||
## Kotlin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will note that the yield points for Kotlin's "coroutines" under the hood are delimited by continuations. Its suspendCoroutine { coroutine -> ... }
isn't far off of await new Promise((resolve, reject) => { ... })
in ECMAScript/JavaScript. Likewise, the end accepts a continuation object (it's an interface) to essentially act like ECMAScript's/JavaScript's promise.then(...)
.
- Its
coroutine.resume(value)
is like ECMAScript'sresolve(value)
. coroutine.resumeWithException(value)
is almost a carbon copy clone ofreject(value)
.
The way sequence
under the hood handles it is by executing the suspend
function with a context that store both the outer and inner continuation objects. So in essence, it's little more than a push-based async
/await
in the end.
This RFC has been accepted by the lang team and has now been merged. Thanks to all those who reviewed this RFC and provided helpful feedback. To keep up with further work on |
@rpjohnst @withoutboats interesting discussion, I hope this won't get lost in this RFC thread! In particular it may be worth thinking a bit more about this concept of the resume argument "matching" the previous yield point. One thing that occurred to me (and I think is more or less explicit in the discussion above) is that general coroutines with yield type However, it turns out in a sense we already have two different kinds of yield points because of laziness! We can view the coroutine as immediately yielding at an "initial" yield point with resume type I've created a Zulip thread. |
Rendered
Tracking issue: rust-lang/rust#117078
This RFC reserves the
gen
keyword in the Rust 2024 edition for generators and addsgen { .. }
blocks to the language. Similar to howasync
blocks produce values that can be awaited with.await
,gen
blocks produce values that can be iterated over withfor
.Writing iterators manually can be painful. Many iterators can be written by chaining together iterator combinators, but some need to be written with a manual implementation of
Iterator
. This can push people to avoid iterators and do worse things such as writing loops that eagerly store values to mutable state. Withgen
blocks, we can now write a simplefor
loop and still get a lazy iterator of values. E.g.: