Expand description
§Crimes - An exploration of (ab)using async/await syntax to simplify writing coroutines
While we wait for the experimental coroutine API to land, this crate offers a way of writing
ergonomic coroutines on stable Rust today without resorting to proc macros or other code generation
techniques. In order to make this work we need to commit a few crimes by way of abusing the fact
that Rust’s async/await syntax de-sugars into a state machine that implements Future. There
are a couple of (documented) foot guns lying around that you need to be wary of but so long as you
are alright with that you can play around with coroutines today.
Please note that the top level API provided by this crate is not 100% compatible with the current API that is proposed on nightly.
§Coroutines from async functions
The Coro struct represents a running coroutine which can be driven externally from by calling
resume. The quickest way to create a Coro is to build one from an async function
with the appropriate signature:
use simple_coro::{Coro, CoroState, Handle};
let mut coro = Coro::from(async |handle: Handle<usize, bool>| {
let say_hello: bool = handle.yield_value(42).await;
if say_hello {
"Hello, World!"
} else {
"Goodbye cruel world!"
}
});
loop {
coro = match coro.resume() {
CoroState::Pending(c, n) => {
assert_eq!(n, 42);
c.send(true)
}
CoroState::Complete(msg) => {
assert_eq!(msg, "Hello, World!");
break;
}
};
}A Coro can be made from any async function that accepts a Handle as its single argument, optionally
returning a value as in the example here (we call this a CoroFn). The Handle argument serves several
purposes:
- The only way to obtain a
Handleis through the construction of aCoroso you will not be able to call this function directly. The (ab)use ofasync/awaitsyntax to make this all work means that theFuturereturned by this function can not be awaited under a normal async runtime so we need to ensure that it is only called as part of running theCoro. It also can’t await arbitrary futures, only those made available as methods on Handle and otherCoroFns. - The two generic types for a
Handle<S, R>specify the values that will be Sent and Received from yield points. - Communication between a running
Coroand the code driving it is possible via methods on aHandle.
Once we have a Coro we are responsible for running it by calling the resume and
send methods: this crate does not provide any form of runtime. resume takes ownership of
a Ready coroutine and gives you back a CoroState that either contains the suspended coroutine along with
the value it emitted from its next yield, or the return value of the coroutine if
it has finished. send takes ownership of a Pending coroutine and stores a response to the last yield
for the coroutine to receive when it is resumed.
The run_sync method on Coro simplifies writing the loop construct in the example
above if you always want to respond to yields synchronously in the same way, and the unwrap
and unwrap_pending methods on CoroState let you avoid having to match if
you know the state you coming back from a particular yield.
§Implementing Coroutines explicitly
If you want to implement a coroutine directly then you can implement either of the AsCoro or IntoCoro traits, depending on whether you need to make use of state held within the implementor or not.
Structs§
- Coro
- A running coroutine that can make progress by calling resume.
- Handle
- A yield handle to facilitate communication between a Coro and the logic driving it.
- Pending
- A Lifecycle marker denoting that a Coro is awaiting a call to send.
- Ready
- A Lifecycle marker denoting that a Coro is ready for the next call to resume.
Enums§
Traits§
- AsCoro
- A type that can construct a Coro.
- Into
Coro - A type that can be converted into a Coro from a value.
- Lifecycle
- The lifecycle state of a running Coro: either Pending or Ready.
Type Aliases§
- CoroFn
- A future that can be executed as a Coro. See AsCoro and IntoCoro for details.
- Generator
- A Generator is a simplified Coro that only emits values and does not return a final value. As such, it can be used as a convenient way to write an Iterator.
- Pending
Coro - A Coro that is pending a response from send.
- Ready
Coro - A Coro that is ready to be resumed by calling resume.