-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking Issue for #![feature(async_iterator)] #79024
Comments
Add `core::stream::Stream` [[Tracking issue: rust-lang#79024](rust-lang#79024)] This patch adds the `core::stream` submodule and implements `core::stream::Stream` in accordance with [RFC2996](rust-lang/rfcs#2996). The RFC hasn't been merged yet, but as requested by the libs team in rust-lang/rfcs#2996 (comment) I'm filing this PR to get the ball rolling. ## Documentatation The docs in this PR have been adapted from [`std::iter`](https://doc.rust-lang.org/std/iter/index.html), [`async_std::stream`](https://docs.rs/async-std/1.7.0/async_std/stream/index.html), and [`futures::stream::Stream`](https://docs.rs/futures/0.3.8/futures/stream/trait.Stream.html). Once this PR lands my plan is to follow this up with PRs to add helper methods such as `stream::repeat` which can be used to document more of the concepts that are currently missing. That will allow us to cover concepts such as "infinite streams" and "laziness" in more depth. ## Feature gate The feature gate for `Stream` is `stream_trait`. This matches the `#[lang = "future_trait"]` attribute name. The intention is that only the APIs defined in RFC2996 will use this feature gate, with future additions such as `stream::repeat` using their own feature gates. This is so we can ensure a smooth path towards stabilizing the `Stream` trait without needing to stabilize all the APIs in `core::stream` at once. But also don't start expanding the API until _after_ stabilization, as was the case with `std::future`. __edit:__ the feature gate has been changed to `async_stream` to match the feature gate proposed in the RFC. ## Conclusion This PR introduces `core::stream::{Stream, Next}` and re-exports it from `std` as `std::stream::{Stream, Next}`. Landing `Stream` in the stdlib has been a mult-year process; and it's incredibly exciting for this to finally happen! --- r? `````@KodrAus````` cc/ `````@rust-lang/wg-async-foundations````` `````@rust-lang/libs`````
Is there a plan to include a |
The plan is to add methods directly onto |
Some discussion on Zulip raised the question of naming. With full acknowledgement to the fact that the name |
As a new, inexperienced user, I find |
Prior art on "iterator" and "async iterator" naming schemes in other languages:
|
Context: We're prototyping some simple CLI app functionality. We're following the mini-redis example code where we can. We've bumped out head on streams, and the need for the crates async-streams, and parallel-streams. In our experience some Given the need for the crates we cited, especially the async-streams RFC, we wonder if there isn't a need for:
Or is the intention that async-stream and parallel-stream crate efforts all able to converge into a |
Rayon provides a whole ecosystem of parallel iterators on top of a work-stealing threadpool, and is currently the de facto Rust standard for parallel iteration. But I don't foresee a parallel iterator trait getting into the Rust standard library anytime soon. Streams are supposed to cover the use case of concurrent iterators only (per my understanding). Hopefully once streams are stabilized into the Rust standard library we can have some syntax to use them in concurrent for loops just like we currently use synchronous iterators in for loops. However, there are still some issues to work out like what to do if a a stream panics or is dropped. Settling on a name (Stream vs AsyncIterator vs ConcurrentIterator) will be the easy part! 😜 Unfortunately, it's difficult to combine parallel and concurrent iteration at the moment. This would require Rayon to support a way to move tasks from worker threads to an async executor thread, or for an async executor like Tokio to support parallel thread pools in a more sophisticated way than |
I've filed a PR to the RFCs repo, updating the "streams" terminology to "async iterator" instead: rust-lang/rfcs#3208. |
why did |
Move `{core,std}::stream::Stream` to `{core,std}::async_iter::AsyncIterator` Following amendments in rust-lang/rfcs#3208. cc rust-lang#79024 cc `@yoshuawuyts` `@joshtriplett`
Move `{core,std}::stream::Stream` to `{core,std}::async_iter::AsyncIterator` Following amendments in rust-lang/rfcs#3208. cc rust-lang#79024 cc ``@yoshuawuyts`` ``@joshtriplett``
One potential concern re: methods on For example, it can be very efficient to perform a My concern is that without parity between the methods on I know for a fact that the current async ecosystem with It would be extremely detrimental to the idea that "this is just the async version of an iterator" if there weren't parity there IMHO, since users might notice slowdowns in code that simply calls |
@clarfonthey yes, definitely. The concerns you raise are valid, and the working group is currently actively investigating how we can ensure parity between sync and async Rust APIs. We don't yet (but should) have guidelines on how methods on async traits should be translated from sync to async, but that likely needs us to land async closures / async traits first. |
Given the current trend of the async working group, I wouldn't be surprised if this can become |
@joshtriplett ah yes, that's definitely something we've been discussing within the working group, and we're currently working towards enabling that. The other thing we're currently researching is keyword-generics, which may allow us to merge the separate I'll update the tracking issue to reflect both these items. |
(NOT A CONTRIBUTION)
I think this is not the right direction for this feature. I hold this view very strongly. AsyncIterator::poll_next enables library authors to write explicit, low-level async iteration primitives for unsafe optimizations. Relying on the compiler generated futures of an async function will make the layout of these types much less predictable or controllable and will take this away from users. For ease of use where this fine-grained laying control is not desirable, generators are the play (async or otherwise), rather than having to write next methods (async or otherwise) at all. You need to have the low level APIs (Future::poll, Iterator::next and AsyncIterator::poll_next) for hand-rolled optimized code that the compiler can't be relied on to generate. You need to have the high level syntax (async functions, generators, and async generators) for when people just want to get things done and don't care about these kinds of optimizations. You need both. An async next method would be doing each side of it only halfway (low-level iterator, high-level asynchrony), and would basically trap Rust in a local maxima that looks appealing from where we are now but would not be the best final state. EDIT: What I mean when I say that "generators are the play" and "local maxima" is that I think because Iterator::next has always existed and has a superficially simple API (ie no Pin, no Context), its not as obvious that for ease of use implementing an iterator with a next method is actually an awful experience for users. Yielding from generators would be much easier. So when you want the ease-of-use story, you want generators, and those can be made async just as easily as functions can. But generators can't guarantee the representation that gets the codegen from for loops over slices looking so good, and similarly won't guarantee the optimizations some async code will want as well. You should be thinking of implementing Iterator::next as really as low-level as implementing Future::poll. EDIT2: I think the counterargument to this is that mixing-and-matching high-level and low-level is also desirable. IE next + async is desirable when you want control over iteration but don't care about control over asynchrony. Analogously, there must be a hypothetical API that's control over asynchrony but compiler generated iteration - a polling generator(??). I think there could be a case to be made that users do want to be able to drop down into fine control over one aspect of their control flow but not the other, but then that should be an additional, third (and fourth?) option in addition to full control or full ease of use, you can't get rid of the full-control option, which is poll_next. |
For the people that haven't closely followed along on the "blogosphere", I'll link to a few excellent blog posts about it (in chronological order) (authored by people in this thread) (by no means exhaustive):
Another argument in favour of async fn next(mut self: Pin<&mut Self>) -> Option<Self::Item> {
poll_fn(|cx| self.as_mut().poll_next(cx)).await
}
// Or
async fn next(&mut self) -> Option<Self::Item>
where
Self: Unpin,
{
poll_fn(|cx| Pin::new(&mut *self).poll_next(cx)).await
} Certainly, this is not as clean as the simple Whether Rust then goes with |
@withoutboats for your latest blog post, can you give an approximate loop desugaring like in #118847 (comment) Anyway, I'm reraising a concern here that I already mentioned in the PR and that's similar to clarfonthey's: I think the current proposed interface is terrible for performance when iterating on small types (e.g. One option is to make Another approach is returning an My async understanding is limited, I'm coming from the sync Iterators side. So I may have misunderstood something. |
After rust 1.75 released, trait async func is stable, |
In the rest of the thread, withoutboats has argued that we need async generators for ergonomics, rather than |
Looking at this through the lens of algebraic effects, I would say that
This analogy is not quite perfect: futures can be polled again after returning "ready" (and similar for iterators); futures have this "context" argument; iterators are not pinned. But I would argue all of those are concessions to how we model these kinds of computations in Rust -- the abstract concept we want to model doesn't have them, but the imperfect realization of that concept in Rust does have them. An We don't have algebraic effects in Rust, but futures have shown how we can model one particular algebraic effect. If we follow that same paradigm, then the In contrast, the So I guess what I am saying is, I tend to agree with boats. Mind you, I'm not an expert in async, I am taking a 10,000 foot view of this problem. |
speaking of https://doc.rust-lang.org/nightly/std/async_iter/index.html#laziness --- can the basic async iterator has a usage example? right now these docs show how to define the counter and implement the async iterator trait, but not how to actually invoke it or use it as an end user. is this because to run this async iterator we need externals like tokio? if so, how do we ensure std has what it needs to run async iterators without needing an external runtime here's the part i mean, the code isn't really showing what to do with the counter, so it's hard to look at this page of rustdoc and know fully how to get rolling with a minimalist async iterator, which tbh is likely to be much more rewarding for all of us than futures, just a hunch, async iterators are the move, because they promote categorical thinking [about i/o] and especially for input streams seems like a no brainer to have the example here be a perfectly optimized copypasta for std-only async iterator over some particular user defined type of messages from a generic tcp stream output buffer of bytes, for example as an aside, i wonder if, in the context of async, some subtle fundamental limitation in Which makes me wonder if pub enum Ratchet<A, B> {
Pending(A),
Ready(B),
} Could give us a different design avenue for async/await besides
because the but maybe
|
@rustbot labels -I-libs-api-nominated +T-lang +WG-async This was discussed briefly in the libs-api call today. This work is going to be driven from the lang side on the basis of work done in WG-async, as we had discussed in the meeting on 2024-05-14. On that basis, the decision was to unnominate. |
Where can I find the minutes of that meeting? In the t-lang/meetings Zulip channel, I can only find the meeting on 2024-05-15, but that was about a different topic (match ergonomics). EDIT: Oh, that was a libs-api meeting, not a lang meeting. oops |
One thing from Yosh's blog post that hasn't been mentioned here is that Another thing is that there's a lot of comparisons being made about the performance of fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
// Do something
// Return Poll<Option<T>>
} with the output of: async fn next(&mut self) -> Option<T> {
future::poll_fn(|cx| /* -> Poll<Option<T>> */ {
// Do something
// Return Poll<Option<T>>
}).await
} Since |
This makes poll_next more powerful. A Stream like https://docs.rs/tokio-util/latest/tokio_util/codec/struct.FramedRead.html currently works with any Similar story is for https://docs.rs/futures-concurrency/latest/futures_concurrency/stream/trait.Merge.html which would now need to cache the futures of each |
The main issue here is that with |
I would be hesitant to take this as an inherent constraint. I'm fairly certain that with unsafe binders we can formulate an implementation of Unsafe binders are going to be a great help for async traits regardless of their shape. They are the same feature required to e.g. make |
If we use |
I believe there might be a miscommunication: I didn't mean to comment on the pinning requirement, I meant to say it shouldn't be necessary to allocate. While immovability may be inherent to self-references, allocation should not be. |
Right. I suggested box-pinning because at present the impl AsyncIterator for Pin<&mut Merge<...>> { ... } otherwise we'd need to instead offer trait AsyncIterator {
async fn next(self: Pin<&mut Self>) -> Option<Self::Item>;
} |
This is a tracking issue for the RFC "2996" (rust-lang/rfcs#2996).
The feature gate for the issue is
#![feature(async_iterator)]
.About tracking issues
Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.
Steps
instructions?)
Unresolved Questions
Stream
andIterator
Addcore::stream::Stream
#79023 (comment)Iterator
, clarifying panic behavior. The panic behavior betweenStream
andIterator
should be consistent.Stream::next
. This was removed from the RFC because it prevents dynamic dispatch, and subsequently removed from the implementation. This should be resolved before stabilizing.fn poll_next
toasync fn next
once we can useasync
in traits.Iterator
andAsyncIterator
into a single trait which is generic over "asyncness".AsyncIterator
instead? Tracking Issue for #![feature(async_iterator)] #79024 (comment)Implementation history
core::stream::{Stream, Next}
Stream
toAsyncIterator
The text was updated successfully, but these errors were encountered: