[−][src]Crate genawaiter
This crate implements generators for Rust. Generators are a feature common across many programming language. They let you yield a sequence of values from a function. A few common use cases are:
- Easily building iterators.
- Avoiding allocating a list for a function which returns multiple values.
Rust has this feature too, but it is currently unstable (and thus nightly-only). But with this crate, you can use them on stable Rust!
Features
This crate has these features:
futures03
(disabled by default) – ImplementsStream
for all generator types. Adds a dependency onfutures-core
.proc_macro
(enabled by default) – Adds support for macros, and adds various compile-time dependencies.
Choose your guarantees
This crate supplies three concrete implementations of generators:
-
genawaiter::stack
– Allocation-free. You should prefer this when possible. -
genawaiter::rc
– This allocates. -
genawaiter::sync
– This allocates, and can be shared between threads.
Here are the differences in table form:
stack::Gen | rc::Gen | sync::Gen | |
---|---|---|---|
Allocations per generator | 0 | 2 | 2 |
Generator can be moved after created | no | yes | yes |
Thread-safe | no | no | yes |
Creating a generator
Once you've chosen how and whether to allocate (see previous section), you can create a
generator using a macro from the gen
family:
let count_to_ten = gen!({ for n in 0..10 { yield_!(n); } });
To re-use logic between multiple generators, you can use a macro from the producer
family, and then pass the producer to Gen::new
.
- [
stack_producer!
] andlet_gen_using!
rc_producer!
andGen::new
sync_producer!
andGen::new
let count_producer = producer!({ for n in 0..10 { yield_!(n); } }); let count_to_ten = Gen::new(count_producer);
If neither of these offers enough control for you, you can always skip the macros and use the low-level API directly:
let count_to_ten = Gen::new(|co| async move { for n in 0..10 { co.yield_(n).await; } });
A tale of three types
A generator can control the flow of up to three types of data:
- Yield – Each time a generator suspends execution, it can produce a value.
- Resume – Each time a generator is resumed, a value can be passed in.
- Completion – When a generator completes, it can produce one final value.
Yield
Values can be yielded from the generator by calling yield_
, and immediately awaiting
the future it returns. You can get these values out of the generator in either of two
ways:
-
Call
resume()
orresume_with()
. The values will be returned in aGeneratorState::Yielded
.let mut generator = gen!({ yield_!(10); }); let ten = generator.resume(); assert_eq!(ten, GeneratorState::Yielded(10));
-
Treat it as an iterator. For this to work, both the resume and completion types must be
()
.let generator = gen!({ yield_!(10); }); let xs: Vec<_> = generator.into_iter().collect(); assert_eq!(xs, [10]);
Resume
You can also send values back into the generator, by using resume_with
. The generator
receives them from the future returned by yield_
.
let mut printer = gen!({ loop { let string = yield_!(()); println!("{}", string); } }); printer.resume_with("hello"); printer.resume_with("world");
Completion
A generator can produce one final value upon completion, by returning it from the
function. The consumer will receive this value as a GeneratorState::Complete
.
let mut generator = gen!({ yield_!(10); "done" }); assert_eq!(generator.resume(), GeneratorState::Yielded(10)); assert_eq!(generator.resume(), GeneratorState::Complete("done"));
Async generators
If you await other futures inside the generator, it becomes an async generator. It
does not makes sense to treat an async generator as an Iterable
, since you cannot
await
an Iterable
. Instead, you can treat it as a Stream
. This requires opting in
to the dependency on futures
with the futures03
feature.
[dependencies]
genawaiter = { version = "...", features = ["futures03"] }
async fn async_one() -> i32 { 1 } async fn async_two() -> i32 { 2 } let gen = gen!({ let one = async_one().await; yield_!(one); let two = async_two().await; yield_!(two); }); let stream = block_on_stream(gen); let items: Vec<_> = stream.collect(); assert_eq!(items, [1, 2]);
Async generators also provide a async_resume
method for lower-level control. (This
works even without the futures03
feature.)
match gen.async_resume().await { GeneratorState::Yielded(_) => {} GeneratorState::Complete(_) => {} }
Backported stdlib types
This crate supplies Generator
and
GeneratorState
. They are copy/pasted from the stdlib (with
stability attributes removed) so they can be used on stable Rust. If/when real
generators are stabilized, hopefully they would be drop-in replacements. Javascript
developers might recognize this as a polyfill.
There is also a Coroutine
trait, which does not come from the
stdlib. A Coroutine
is a generalization of a Generator
. A Generator
constrains the
resume argument type to ()
, but in a Coroutine
it can be anything.
Modules
rc | This module implements a generator which stores its state on the heap. |
stack | This module implements a generator which doesn't allocate. |
sync | This module implements a generator which can be shared between threads. |
Macros
generator_mut | Deprecated Creates a generator on the stack. |
rc_producer | Creates a producer for use with |
sync_producer | Creates a producer for use with |
unsafe_create_generator | Deprecated Creates a generator on the stack unsafely. |
yield_ | Yields a value from a generator. |
Enums
GeneratorState | The result of a generator resumption. |
Traits
Coroutine | A trait implemented for coroutines. |
Generator | A trait implemented for generator types. |