|
| 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