Async iteration

Impact

  • Able to write code that takes "something iterable"
  • Able to use combinators similar to synchronous Iterator
  • Able to construct complex, parallel schedules that can refer to borrow data

Requires

Design notes

The async iterator trait can leverage inline async functions:

#![allow(unused)]
fn main() {
#[repr(inline_async)]
trait AsyncIterator {
    type Item;

    async fn next(&mut self) -> Self::Item;
}
}

Note the name change from Stream to AsyncIterator.

One implication of this change is that pinning is no longer necessary when driving an async iterator. For example, one could now write an async iterator that recursively walks through a set of URLs like so (presuming std::async_iter::from_fn and async closures):

#![allow(unused)]
fn main() {
fn explore(start_url: Url) -> impl AsyncIterator {
    let mut urls = vec![start_url];
    std::async_iter::from_fn(async move || {
        if let Some(url) = urls.pop() {
            let mut successor_urls = fetch_successor_urls(url).await;
            urls.extend(successor_urls);
            Some(url)
        } else {
            None
        }
    })
}
}

Parallel async iteration

We should have combinators like buffered that enable parallel async iteration, similar to the parallel iterators offered by [rayon]. The core operation here is for_each (which processes each item in the iterator):

#![allow(unused)]
fn main() {
trait ParAsyncIter {
    type Item;

    async fn for_each(&mut self, op: impl AsyncFn(Self::Item));
}
}

The buffered combinator would be implemented by creating an internal scope and spawning tasks into it as needed.