Expand description
§Convert async functions to generators on stable Rust
This crate allows to write generator-like async code to implement Iterator
on today’s (August 2024) stable Rust.
§License
This crate is dual-licensed under the terms of both Apache 2.0 and MIT licenses.
§Usage example
use async_to_iter::{IterSink, make_iter};
// Async code that implements the iterator.
async fn count_to_impl(sink: IterSink<u32>, n: u32) {
for i in 1..=n {
sink.yield_value(i).await;
}
}
// Function that constructs the iterator from async code using `make_iter()`.
fn count_to(n: u32) -> impl Iterator<Item = u32> {
make_iter(move |sink| count_to_impl(sink, n))
}
fn main() {
// The resulting iterator can be accessed as usual.
let mut iter = count_to(3);
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), None);
}§FAQ
§How does this work?
The compiler automatically converts async code to a state machine that saves its internal
state across await points. This is how async code has long been implemented in Rust.
make_iter returns a type implementing Iterator that translates Iterator::next() to Future::poll() calls.
Yielding value and suspending the future in yield points is implemented by IterSink::yield_value().
It saves the value provided by async code (it will later be returned from Iterator::next())
and returns a Future that becomes ready the second time Future::poll is called.
This way, execution of async code pauses exactly once each time a value is yielded.
§Does this crate use unsafe?
It uses it just for one thing: to create a no-op Waker.
It is safe because a no-op waker does nothing, and it actually re-implements an unstable safe functions
from the standard library: Waker::noop().
The rest of this crate only uses safe Rust code.
§Is this a zero-cost abstraction?
Unfortunately, no. The future that yields iterator output values is stored on heap — this is necessary to
pin it without affecting the usability of the Iterator implementation returned from make_iter().
It also makes some other allocations now. The number of allocations may be optimized in the future,
but it is unlikely that this will become a zero-cost abstraction.
If you need zero-cost generators in Rust, you will likely have to use some Nightly features.
§Does this work in #[no_std] environments?
Yes, this crate is #[no_std]. However, it needs the alloc crate and a global allocator.
Structs§
- Iter
Sink - Sink for iterator output values to be used in async code.
Functions§
- make_
iter - Create an iterator implemented by async code.