|
| 1 | +- Feature Name: `async_stream` |
| 2 | +- Start Date: 2020-05-13 |
| 3 | +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) |
| 4 | +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Introduce the `Stream` trait into the standard library, using the |
| 10 | +design from `futures`. Redirect the futures-stream definition to the |
| 11 | +standard library. |
| 12 | + |
| 13 | +# Motivation |
| 14 | +[motivation]: #motivation |
| 15 | + |
| 16 | +* Why include stream trait in the std library at all? |
| 17 | + * Streams are a core async abstraction |
| 18 | + * we want to enable portable libraries that produce/consume streams without being tied to particular executors |
| 19 | + * examples of crates that are consuming streams? |
| 20 | + * [async-h1](https://docs.rs/async-h1)'s server implementation takes `TcpStream` instances produced by a `TcpListener` in a loop. |
| 21 | + * examples of crates that are producing streams? |
| 22 | + * [async-sse](https://docs.rs/async-sse/) parses incoming buffers into a stream of messages. |
| 23 | + * people can do this today using futures crate, but the stability guarantees are less clear |
| 24 | + * e.g., if tokio wishes to declare a [5 year stability period](http://smallcultfollowing.com/babysteps/blog/2020/02/11/async-interview-6-eliza-weisman/#communicating-stability), having something in std means there are no concerns about trait changing during that time ([citation](http://smallcultfollowing.com/babysteps/blog/2019/12/23/async-interview-3-carl-lerche/#what-should-we-do-next-stabilize-stream)) |
| 25 | + * We eventually want dedicated syntax for working with streams, which will require a shared trait |
| 26 | + * Producing streams |
| 27 | + * Consuming streams |
| 28 | +* Why is the stream trait defined how it is? |
| 29 | + * It is the "pollable iterator" |
| 30 | + * dyn compatibility |
| 31 | + |
| 32 | +# Guide-level explanation |
| 33 | +[guide-level-explanation]: #guide-level-explanation |
| 34 | + |
| 35 | +A "stream" is the async version of an iterator. The `Stream` trait |
| 36 | +matches the definition of an [iterator], except that the `next` method |
| 37 | +is defined to "poll" for the next item. In other words, where the |
| 38 | +`next` method on an iterator simply computes (and returns) the next |
| 39 | +item in the sequence, the `poll_next` method on stream asks if the |
| 40 | +next item is ready. If so, it will be returned, but otherwise |
| 41 | +`poll_next` will return [`Poll::pending`]. Just as with a [`Future`], |
| 42 | +returning [`Poll::pending`] implies that the stream has arranged for |
| 43 | +the current task to be re-awoken when the data is ready. |
| 44 | + |
| 45 | +[iterator]: https://doc.rust-lang.org/std/iter/trait.Iterator.html |
| 46 | +[`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html |
| 47 | +[`Poll::pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending |
| 48 | + |
| 49 | +```rust |
| 50 | +// Defined in std::stream module |
| 51 | +pub trait Stream { |
| 52 | + type Item; |
| 53 | + |
| 54 | + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>; |
| 55 | + |
| 56 | + #[inline] |
| 57 | + fn size_hint(&self) -> (usize, Option<usize>) { |
| 58 | + (0, None) |
| 59 | + } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +The arguments to `poll_next` match that of the [`Future::poll`] method: |
| 64 | + |
| 65 | +* The self must be a pinned reference, ensuring both unique access to |
| 66 | + the stream and that the stream value itself will not move. Pinning |
| 67 | + allows the stream to save pointers into itself when it suspends, |
| 68 | + which will be required to support generator syntax at some point. |
| 69 | +* The [context] `cx` defines details of the current task. In particular, |
| 70 | + it gives access to the [`Waker`] for the task, which will allow the |
| 71 | + task to be re-awoken once data is ready. |
| 72 | + |
| 73 | +[`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll |
| 74 | +[pinned]: https://doc.rust-lang.org/std/pin/struct.Pin.html |
| 75 | +[context]: https://doc.rust-lang.org/std/task/struct.Context.html |
| 76 | +[Waker]: https://doc.rust-lang.org/std/task/struct.Waker.html |
| 77 | + |
| 78 | +## Initial impls |
| 79 | + |
| 80 | +There are a number of simple "bridge" impls that are also provided: |
| 81 | + |
| 82 | +```rust |
| 83 | +impl<S> Stream for Box<S> |
| 84 | +where |
| 85 | + S: Stream + Unpin + ?Sized, |
| 86 | +{ |
| 87 | + type Item = <S as Stream>::Item |
| 88 | +} |
| 89 | + |
| 90 | +impl<S> Stream for &mut S |
| 91 | +where |
| 92 | + S: Stream + Unpin + ?Sized, |
| 93 | +{ |
| 94 | + type Item = <S as Stream>::Item; |
| 95 | +} |
| 96 | + |
| 97 | +impl<S, T> Stream for Pin<P> |
| 98 | +where |
| 99 | + P: DerefMut<Target=T> + Unpin, |
| 100 | + T::Target: Stream, |
| 101 | +{ |
| 102 | + type Item = <T as Stream>::Item; |
| 103 | +} |
| 104 | + |
| 105 | +impl<S> Stream for AssertUnwindSafe<S> |
| 106 | +where |
| 107 | + S: Stream, |
| 108 | +{ |
| 109 | + type Item = <S as Stream>::Item; |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +# Reference-level explanation |
| 114 | +[reference-level-explanation]: #reference-level-explanation |
| 115 | + |
| 116 | +This section goes into details about various aspects of the design and |
| 117 | +why they ended up the way they did. |
| 118 | + |
| 119 | +## Where does `Stream` live in the std lib? |
| 120 | + |
| 121 | +`Stream` will live in the `core::stream` module and be re-exported as `std::stream`. |
| 122 | + |
| 123 | +## Why use a `poll` method? |
| 124 | + |
| 125 | +An alternative design for the stream trait would be to have a trait |
| 126 | +that defines an async `next` method: |
| 127 | + |
| 128 | +```rust |
| 129 | +trait Stream { |
| 130 | + type Item; |
| 131 | + |
| 132 | + async fn next(&mut self) -> Option<Self::Item>; |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +Unfortunately, async methods in traits are not currently supported, |
| 137 | +and there [are a number of challenges to be |
| 138 | +resolved](https://rust-lang.github.io/wg-async-foundations/design_notes/async_fn_in_traits.html) |
| 139 | +before they can be added. |
| 140 | + |
| 141 | +Moreover, it is not clear yet how to make traits that contain async |
| 142 | +functions be `dyn` safe, and it is imporant to be able to pass around `dyn |
| 143 | +Stream` values without the need to monomorphize the functions that work |
| 144 | +with them. |
| 145 | + |
| 146 | +Unfortunately, the use of poll does mean that it is harder to write |
| 147 | +stream implementations. The long-term fix for this, discussed in the [Future possibilities][future-possibilities] section, is dedicated [generator syntax]. |
| 148 | + |
| 149 | +# Drawbacks |
| 150 | +[drawbacks]: #drawbacks |
| 151 | + |
| 152 | +Why should we *not* do this? |
| 153 | + |
| 154 | +# Rationale and alternatives |
| 155 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 156 | + |
| 157 | +## Where should stream live? |
| 158 | + |
| 159 | +* core::stream is analogous to core::future |
| 160 | +* but do we want to find some other naming scheme that can scale up to other future additions, such as io traits or channels? |
| 161 | + |
| 162 | +# Prior art |
| 163 | +[prior-art]: #prior-art |
| 164 | + |
| 165 | +Discuss prior art, both the good and the bad, in relation to this proposal. |
| 166 | +A few examples of what this can include are: |
| 167 | + |
| 168 | +- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? |
| 169 | +- For community proposals: Is this done by some other community and what were their experiences with it? |
| 170 | +- For other teams: What lessons can we learn from what other communities have done here? |
| 171 | +- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. |
| 172 | + |
| 173 | +This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. |
| 174 | +If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. |
| 175 | + |
| 176 | +Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. |
| 177 | +Please also take into consideration that rust sometimes intentionally diverges from common language features. |
| 178 | + |
| 179 | +# Unresolved questions |
| 180 | +[unresolved-questions]: #unresolved-questions |
| 181 | + |
| 182 | +- What parts of the design do you expect to resolve through the RFC process before this gets merged? |
| 183 | +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? |
| 184 | +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? |
| 185 | + |
| 186 | +# Future possibilities |
| 187 | +[future-possibilities]: #future-possibilities |
| 188 | + |
| 189 | +## Convenience methods |
| 190 | + |
| 191 | +The `Iterator` trait defines a number of useful combinators, like |
| 192 | +`map`. The `Stream` trait being proposed here does not include any |
| 193 | +such conveniences. Instead, they are available via extension traits, |
| 194 | +such as the [`StreamExt`] trait offered by the [`futures`] crate. |
| 195 | + |
| 196 | +[`StreamExt`]: https://docs.rs/futures/0.3.5/futures/stream/trait.StreamExt.html |
| 197 | +[`futures`]: https://crates.io/crates/futures |
| 198 | + |
| 199 | +The reason that we have chosen to exclude combinators is that a number |
| 200 | +of them would require access to async closures. As of this writing, |
| 201 | +async closures are unstable and there are a number of [outstanding |
| 202 | +design issues] to be resolved before they are added. Therefore, we've |
| 203 | +decided to enable progress on the stream trait by stabilizing a core, |
| 204 | +and to come back to the problem of extending it with combinators. |
| 205 | + |
| 206 | +[outstanding design issues]: https://rust-lang.github.io/wg-async-foundations/design_notes/async_closures.html |
| 207 | + |
| 208 | +Another reason to defer adding combinators is because of the possibility |
| 209 | +that some combinators may work best |
| 210 | + |
| 211 | +This path does carry some risk. Adding combinator methods can cause |
| 212 | +existing code to stop compiling due to the ambiguities in method |
| 213 | +resolution. We have had problems in the past with attempting to migrate |
| 214 | +iterator helper methods from `itertools` for this same reason. |
| 215 | + |
| 216 | +While such breakage is technically permitted by our semver guidelines, |
| 217 | +it would obviously be best to avoid it, or at least to go to great |
| 218 | +lengths to mitigate its effects. One option would be to extend the |
| 219 | +language to allow method resolution to "favor" the extension trait in |
| 220 | +existing code, perhaps as part of an edition migration. |
| 221 | + |
| 222 | +Designing such a migration feature is out of scope for this RFC. |
| 223 | + |
| 224 | +## IntoStream / FromStream traits, mirroring iterators |
| 225 | + |
| 226 | +* currently blocked on async fn in traits |
| 227 | +* The exact bounds are unclear. |
| 228 | +* the same as combinators |
| 229 | +* These would be needed to provide similar iteration semantics as Iterator: |
| 230 | + * `for x in iter` uses `impl IntoIterator for T` |
| 231 | + * `for x in &iter` uses `impl IntoIterator for &T` |
| 232 | + * `for x in &mut iter` uses `impl IntoIterator for &mut T` |
| 233 | + |
| 234 | +## Async iteration syntax |
| 235 | + |
| 236 | +We may wish to introduce some dedicated syntax, analogous to `for` |
| 237 | + |
| 238 | +## Generator syntax |
| 239 | +[generator syntax]: #generator-syntax |
| 240 | + |
| 241 | +```rust |
| 242 | +gen async fn foo() -> X { |
| 243 | + yield value; |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +## "Attached" streams |
| 248 | + |
| 249 | +Just as with iterators, there is a |
| 250 | + |
| 251 | + |
0 commit comments