Skip to content

Commit a1bc4d8

Browse files
authored
Merge pull request #10 from nikomatsakis/stream-rfc
Draft RFC to add `Stream` to the standard library
2 parents b38e8df + 77b4840 commit a1bc4d8

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed

rfc-drafts/stream.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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

Comments
 (0)