Skip to content

Commit 9a59d69

Browse files
committedAug 28, 2017
Auto merge of #43076 - Zoxc:gen, r=arielb1
Generator support This adds experimental support for generators intended to land once rust-lang/rfcs#2033 is approved. This is not yet ready to be merged. Things to do: - [x] Make closure arguments on generators an error - [x] Spot FIXMEs - [x] Pass make tidy - [x] Write tests - [x] Document the current syntax and semantics for generators somewhere - [x] Use proper error message numbers - [x] ~~Make the implicit argument type default to `()`~~
2 parents 5caca6f + a996d5e commit 9a59d69

File tree

164 files changed

+5053
-471
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

164 files changed

+5053
-471
lines changed
 

‎src/Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
# `generators`
2+
3+
The tracking issue for this feature is: [#43122]
4+
5+
[#34511]: https://github.com/rust-lang/rust/issues/43122
6+
7+
------------------------
8+
9+
The `generators` feature gate in Rust allows you to define generator or
10+
coroutine literals. A generator is a "resumable function" that syntactically
11+
resembles a closure but compiles to much different semantics in the compiler
12+
itself. The primary feature of a generator is that it can be suspended during
13+
execution to be resumed at a later date. Generators use the `yield` keyword to
14+
"return", and then the caller can `resume` a generator to resume execution just
15+
after the `yield` keyword.
16+
17+
Generators are an extra-unstable feature in the compiler right now. Added in
18+
[RFC 2033] they're mostly intended right now as a information/constraint
19+
gathering phase. The intent is that experimentation can happen on the nightly
20+
compiler before actual stabilization. A further RFC will be required to
21+
stabilize generators/coroutines and will likely contain at least a few small
22+
tweaks to the overall design.
23+
24+
[RFC 2033]: https://github.com/rust-lang/rfcs/pull/2033
25+
26+
A syntactical example of a generator is:
27+
28+
```rust
29+
#![feature(generators, generator_trait)]
30+
31+
use std::ops::{Generator, GeneratorState};
32+
33+
fn main() {
34+
let mut generator = || {
35+
yield 1;
36+
return "foo"
37+
};
38+
39+
match generator.resume() {
40+
GeneratorState::Yielded(1) => {}
41+
_ => panic!("unexpected value from resume"),
42+
}
43+
match generator.resume() {
44+
GeneratorState::Complete("foo") => {}
45+
_ => panic!("unexpected value from resume"),
46+
}
47+
}
48+
```
49+
50+
Generators are closure-like literals which can contain a `yield` statement. The
51+
`yield` statement takes an optional expression of a value to yield out of the
52+
generator. All generator literals implement the `Generator` trait in the
53+
`std::ops` module. The `Generator` trait has one main method, `resume`, which
54+
resumes execution of the generator at the previous suspension point.
55+
56+
An example of the control flow of generators is that the following example
57+
prints all numbers in order:
58+
59+
```rust
60+
#![feature(generators, generator_trait)]
61+
62+
use std::ops::Generator;
63+
64+
fn main() {
65+
let mut generator = || {
66+
println!("2");
67+
yield;
68+
println!("4");
69+
};
70+
71+
println!("1");
72+
generator.resume();
73+
println!("3");
74+
generator.resume();
75+
println!("5");
76+
}
77+
```
78+
79+
At this time the main intended use case of generators is an implementation
80+
primitive for async/await syntax, but generators will likely be extended to
81+
ergonomic implementations of iterators and other primitives in the future.
82+
Feedback on the design and usage is always appreciated!
83+
84+
### The `Generator` trait
85+
86+
The `Generator` trait in `std::ops` currently looks like:
87+
88+
```
89+
# #![feature(generator_trait)]
90+
# use std::ops::GeneratorState;
91+
92+
pub trait Generator {
93+
type Yield;
94+
type Return;
95+
fn resume(&mut self) -> GeneratorState<Self::Yield, Self::Return>;
96+
}
97+
```
98+
99+
The `Generator::Yield` type is the type of values that can be yielded with the
100+
`yield` statement. The `Generator::Return` type is the returned type of the
101+
generator. This is typically the last expression in a generator's definition or
102+
any value passed to `return` in a generator. The `resume` function is the entry
103+
point for executing the `Generator` itself.
104+
105+
The return value of `resume`, `GeneratorState`, looks like:
106+
107+
```
108+
pub enum GeneratorState<Y, R> {
109+
Yielded(Y),
110+
Complete(R),
111+
}
112+
```
113+
114+
The `Yielded` variant indicates that the generator can later be resumed. This
115+
corresponds to a `yield` point in a generator. The `Complete` variant indicates
116+
that the generator is complete and cannot be resumed again. Calling `resume`
117+
after a generator has returned `Complete` will likely result in a panic of the
118+
program.
119+
120+
### Closure-like semantics
121+
122+
The closure-like syntax for generators alludes to the fact that they also have
123+
closure-like semantics. Namely:
124+
125+
* When created, a generator executes no code. A closure literal does not
126+
actually execute any of the closure's code on construction, and similarly a
127+
generator literal does not execute any code inside the generator when
128+
constructed.
129+
130+
* Generators can capture outer variables by reference or by move, and this can
131+
be tweaked with the `move` keyword at the beginning of the closure. Like
132+
closures all generators will have an implicit environment which is inferred by
133+
the compiler. Outer variables can be moved into a generator for use as the
134+
generator progresses.
135+
136+
* Generator literals produce a value with a unique type which implements the
137+
`std::ops::Generator` trait. This allows actual execution of the generator
138+
through the `Generator::resume` method as well as also naming it in return
139+
types and such.
140+
141+
* Traits like `Send` and `Sync` are automatically implemented for a `Generator`
142+
depending on the captured variables of the environment. Unlike closures though
143+
generators also depend on variables live across suspension points. This means
144+
that although the ambient environment may be `Send` or `Sync`, the generator
145+
itself may not be due to internal variables live across `yield` points being
146+
not-`Send` or not-`Sync`. Note, though, that generators, like closures, do
147+
not implement traits like `Copy` or `Clone` automatically.
148+
149+
* Whenever a generator is dropped it will drop all captured environment
150+
variables.
151+
152+
Note that unlike closures generators at this time cannot take any arguments.
153+
That is, generators must always look like `|| { ... }`. This restriction may be
154+
lifted at a future date, the design is ongoing!
155+
156+
### Generators as state machines
157+
158+
In the compiler generators are currently compiled as state machines. Each
159+
`yield` expression will correspond to a different state that stores all live
160+
variables over that suspension point. Resumption of a generator will dispatch on
161+
the current state and then execute internally until a `yield` is reached, at
162+
which point all state is saved off in the generator and a value is returned.
163+
164+
Let's take a look at an example to see what's going on here:
165+
166+
```rust
167+
#![feature(generators, generator_trait)]
168+
169+
use std::ops::Generator;
170+
171+
fn main() {
172+
let ret = "foo";
173+
let mut generator = move || {
174+
yield 1;
175+
return ret
176+
};
177+
178+
generator.resume();
179+
generator.resume();
180+
}
181+
```
182+
183+
This generator literal will compile down to something similar to:
184+
185+
```rust
186+
#![feature(generators, generator_trait)]
187+
188+
use std::ops::{Generator, GeneratorState};
189+
190+
fn main() {
191+
let ret = "foo";
192+
let mut generator = {
193+
enum __Generator {
194+
Start(&'static str),
195+
Yield1(&'static str),
196+
Done,
197+
}
198+
199+
impl Generator for __Generator {
200+
type Yield = i32;
201+
type Return = &'static str;
202+
203+
fn resume(&mut self) -> GeneratorState<i32, &'static str> {
204+
use std::mem;
205+
match mem::replace(self, __Generator::Done) {
206+
__Generator::Start(s) => {
207+
*self = __Generator::Yield1(s);
208+
GeneratorState::Yielded(1)
209+
}
210+
211+
__Generator::Yield1(s) => {
212+
*self = __Generator::Done;
213+
GeneratorState::Complete(s)
214+
}
215+
216+
__Generator::Done => {
217+
panic!("generator resumed after completion")
218+
}
219+
}
220+
}
221+
}
222+
223+
__Generator::Start(ret)
224+
};
225+
226+
generator.resume();
227+
generator.resume();
228+
}
229+
```
230+
231+
Notably here we can see that the compiler is generating a fresh type,
232+
`__Generator` in this case. This type has a number of states (represented here
233+
as an `enum`) corresponding to each of the conceptual states of the generator.
234+
At the beginning we're closing over our outer variable `foo` and then that
235+
variable is also live over the `yield` point, so it's stored in both states.
236+
237+
When the generator starts it'll immediately yield 1, but it saves off its state
238+
just before it does so indicating that it has reached the yield point. Upon
239+
resuming again we'll execute the `return ret` which returns the `Complete`
240+
state.
241+
242+
Here we can also note that the `Done` state, if resumed, panics immediately as
243+
it's invalid to resume a completed generator. It's also worth noting that this
244+
is just a rough desugaring, not a normative specification for what the compiler
245+
does.

0 commit comments

Comments
 (0)
Please sign in to comment.