Expand description
Coroutines/generators via code transformation, without async.
Annotate a function with coroutine and it is rewritten into
a state machine implementing the Coroutine trait. Suspension
points are written as yield_!(expr); the body is analyzed as a
control-flow graph and rewritten at compile time — no async, no
allocation, no unsafe code.
use diapause::{Coroutine, CoroutineState};
#[diapause::coroutine(yield = u32, resume = u32)]
fn running_total(start: u32) -> u32 {
let a = yield_!(start);
let b = yield_!(start + a);
start + a + b
}
let mut c = running_total(100);
assert_eq!(c.start(), CoroutineState::Yielded(100));
assert_eq!(c.resume(1), CoroutineState::Yielded(101));
assert_eq!(c.resume(2), CoroutineState::Complete(103));Calling the annotated function returns the initial state without
running any code; Coroutine::start runs the body up to the first
yield_!, and each Coroutine::resume continues from the previous
suspension point, passing its argument as the value of the
let x = yield_!(..) binding.
The playground runs the transform in the browser, showing the expanded code and control-flow graph for any annotated function.
§Supported bodies
yield_! works inside if / match / loop / while /
while let / for at any nesting depth, mixed with break,
continue, early return, and the ? operator on Result and
Option. yield_all! delegates to another coroutine, forwarding
its yields and resume values (yield_all_resume! does the same
for one that is already started, and the box modifier stores the
delegate boxed, enabling recursion). Because the state enum stores only
concrete types, serde derives work with their ordinary semantics and
a suspended coroutine can be serialized, deserialized elsewhere, and
resumed — nested delegation states included.
The macro never sees rustc’s type information and works purely
syntactically, which imposes rules on the body: yield_! is
statement-position only, variables held across a suspension point
need syntactically determinable types, and only direct borrows may
cross a yield. See coroutine for the full constraint list
with workarounds.
§Comparison with async-based generators
Crates like genawaiter implement generators by driving an async
block. diapause instead generates the state machine itself, which
means no Pin (states are always Unpin), resume arguments that
are plain function arguments rather than shared-cell tricks, an
inspectable state enum that supports #[derive(Clone)] snapshots
and serde persistence — at the price of the syntactic rules above.
Macros§
- yield_
- Marks a suspension point inside a
#[diapause::coroutine]function. - yield_
all - Delegates to another coroutine inside a
#[diapause::coroutine]function (the analogue of Python’syield from). - yield_
all_ resume - Delegates to an already-started coroutine inside a
#[diapause::coroutine]function.
Structs§
- Fingerprint
Mismatch - A persisted coroutine state does not match the source of the coroutine it is checked against.
- Iter
- A wrapper that implements
Iteratorfor a coroutine.
Enums§
- Coroutine
State - The result of a
Coroutine::start/Coroutine::resumecall. - Coroutine
Status - The result of a
Coroutine::statusquery.
Traits§
- Coroutine
- A resumable computation.
- Fingerprinted
- Fingerprint validation of a persisted coroutine state.
Attribute Macros§
- coroutine
- Transforms a function into a coroutine state machine.