--- title: "Async iterator or Stream exploration doc" tags: ["WG-async", "analysis"] date: 2024-01-05 url: https://hackmd.io/1jmGsVS_Tv-4V7QdKyRldA --- # Async iteratation exploration doc ## What is this A document to explore the design space for async iterators / streams (there may or may not be a single concept here!). ## Code examples This section lists out various properties that the design could support. They are not ordered, and they may or may not all be things that we could satisfy simultaneously. Not everybody necessarily wants all of them. ### Process-each-item We want people to be able to iterate and process each item in the stream. ```rust // something like this.... for await item in async_iter { ... } ``` Another option is a `for_each` method: ```rust async_iter.for_each(async |item| { }) ``` ### While let Analogy to sync iterators suggests people may attempt to write things like this ```rust! while let Some(item) = async_iter.next().await { .... } ``` ### First-of-many-things It's common to want to process each item in an async iterator but also do other things in the meantime, or to merge multiple iterators and take the first item available from any of them. One way to do this today is select: ```rust select! { iter1.next() => ... iter2.next() => ... } ``` Yosh highlighted another option, [merging streams](https://blog.yoshuawuyts.com/futures-concurrency-3/#concurrent-stream-processing-with-stream-merge): ```rust iter1.merge(iter2) ``` ### Async generators passed to a function It should be possible to call functions with generators... ```rust foo(async gen { ... }) ``` ...where those functions are declared in some normalish way: ```rust fn foo(x: impl AsyncIterator<Item = ...>) {...} // ^^^^^^^^^^^^^ maybe `IntoAsyncIterator`? ``` ### Sync generators passed to a function Sync version of the above would probably be.... ```rust foo(gen { ... }) ``` and ```rust fn foo(x: impl IntoIterator<Item = ...>) {...} ``` ### Borrow across a yield in async (or sync) generator ```rust async gen { for x in &mut something { yield x.some_mut_method() } } ``` or ```rust gen { for x in &mut something { yield x.some_mut_method() } } ``` ## Desired properties This section lists out various properties that the design could have. They are not ordered, and they may or may not all be things that we could satisfy simultaneously. Not everybody necessarily wants all of them. ### Analogous with sync iterators All things being equal, it's better if "async patterns" and "sync patterns" are clearly analogous. ### Easy to have async generators ### Easy to manually implement It'd be nice if it were "as easy" to implement async iterators as it is sync ones. That said, generators may make this less important. ```rust struct AsyncRange { from: u32, to: u32, } impl AsyncIterator for AsyncRange { async fn next(&mut self) -> Option<u32> { if self.from >= self.to { None } else { let f = self.from; self.from += 1; Some(f) } } } ``` ### Virtual dispatch We want people to be able to use `dyn TheTrait` with minimal overhead. Example: ```rust fn ``` ### Cancel safety of the `next` option With futures streams, it is common to combine `next` futures and select: ```rust! let x: impl Stream = ...; select! { x.next() => ... other_stuff => ... } // I know I'm getting the syntax wrong... ``` this is convenient because it allows you to respond to the "next item" in the stream or This may however be less important when not relying on `select!` for concurrency, but using dedicated async concurrency operations instead. ### Guarantee forwarding `async Drop` If a type implements `async Drop`, it should be possible to hold inside of an async iterator impl, and guarantee it is forwarded. That should also be the case for async `for..in` loops and async generator blocks and functions. ### Composition with lending, fallibility, pinning, non-send, etc. We know there is ecosystem demand for lending versions of iterators, fallible iterators, pinned iterators, and more. We should have a way to provide all of these in a way that ends up feeling consistent ## Draft designs ## Appendix: Related work * The existing Stream trait from futures crate * Rayon's ParallelIterator trait * Kotlin's [asynchronous flows](https://kotlinlang.org/docs/flow.html) * [`async-iterator` crate](https://docs.rs/async-iterator/latest/async_iterator/index.html) * [`lending-iterator` crate](https://docs.rs/lending-iterator/latest/lending_iterator/) * [`fallible-iterator` crate](https://docs.rs/fallible-iterator/latest/fallible_iterator/index.html) * Something from Swift?? * Other things?? ## Appendix: Links to blog posts