Do not wait for Rust generators
What is generator?
Generators are a pattern widely used in the Python ecosystem, like iterators, they are very useful to produce multiple values. But unlike iterators, they work by interrupting the execution of the function, and allows the caller to resume its execution later:
The yield
keyword interrupts the function and produce the supplied value. Now, instead of using the next()
function to resume the generator, you can use:
gen.send(value)
: theyield
keyword will returnvalue
when resuminggen.throw(exc)
: theyield
keyword will throwexc
when resuminggen.close()
: the generator won’t resume (and raise aStopIteration
)
NB: This is also called a coroutine.
They are a powerful tool. In fact, they were used for concurrency before the rise of async
/await
!
Unfortunately, at the time of writing, this is still an unstable feature in Rust.
Introducing… genawaiter!
This crate gives you, thanks to some useful macros, the unstable feature in stable Rust:
Just like Python, the yield_!
macro will interrupt the execution of the code block, the gen.resume()
function will return a GeneratorState
enum with the yielded value or the final return value.
If called with gen.resume_with(arg)
, the yield_!
macro will return arg
.
One last trick…
Every generators implement the trait Generator<Yield = Y, Return = R>
. If, like me, you have some Python background, you’d like to return such generators, possibly make a trait with a function to return such generators, the problem here is:
- you can’t use
impl Trait
in type-aliases or in trait function signatures (unstable feature) - you can’t return a local variable as a
&dyn Trait
(the variable goes out of scope, this will never be a feature thankfully)
At the moment, one solution is to move the generator into a Box<dyn Trait>
:
Conclusion
You don’t need to wait to use this feature, genawaiter aims to be a “drop-in” replacement of the currently unstable feature. Once it’s generally available, it should be a matter of renaming imports.