Skip to content

Commit e7f148f

Browse files
authored
Merge pull request #18 from nellshamrell/update-async-draft
adds updates based on lang team meeting 2020-08-26
2 parents 6dd2c54 + 399be04 commit e7f148f

File tree

1 file changed

+128
-6
lines changed

1 file changed

+128
-6
lines changed

rfc-drafts/stream.md

+128-6
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,9 @@ pub trait Stream {
7373
fn next(&mut self) -> Next<'_, Self>
7474
where
7575
Self: Unpin;
76+
{ ... }
7677
}
7778
```
78-
* For information on why `Self: Unpin` is included in the `next`
79-
method, please see [this discussion on the draft RFC](https://github.com/rust-lang/wg-async-foundations/pull/15#discussion_r452482084).
8079

8180
The arguments to `poll_next` match that of the [`Future::poll`] method:
8281

@@ -183,6 +182,43 @@ while let Some(v) = stream.next().await {
183182
}
184183
```
185184

185+
We could also consider adding a try_next? function (similar to the one in the [futures-rs](https://docs.rs/futures/0.3.5/futures/stream/trait.TryStreamExt.html#method.try_next) crate, allowing
186+
a user to write:
187+
188+
```rust
189+
while let Some(x) = s.try_next().await?
190+
```
191+
192+
Adding the `try_next` method is out of the scope of this RFC to keep us focused
193+
on adding the critical methods needed for async streams first, then adding in
194+
additional ones at a later date.
195+
196+
One thing to note, if a user is using an older version of `futures-util`,
197+
they would experience ambiguity when trying to use the `next` method that
198+
is added to the standard library (and redirected to from `futures-core`).
199+
200+
This can be done as a non-breaking change, but would require everyone to
201+
upgrade rustc. We will want to create a transition plan on what this
202+
means for users and pick the timing carefully.
203+
204+
### Why does next require Self:Unpin?
205+
206+
When drafting this RFC, there was a [good deal of discussion](https://github.com/rust-lang/wg-async-foundations/pull/15#discussion_r452482084) around why the `next` method requires `Self:Unpin`.
207+
208+
To understand this, it helps to take a closer look at the definition of `Next` (this struct is further discussed later in this RFC) in the [futures-util crate](https://docs.rs/futures-util/0.3.5/src/futures_util/stream/stream/next.rs.html#10-12).
209+
210+
```rust
211+
pub struct Next<'a, St: ?Sized> {
212+
stream: &'a mut St,
213+
}
214+
```
215+
Since `Stream::poll_next` takes a pinned reference, the next future needs `S` to be `Unpin` in order to safely construct a `Pin<&mut S>` from a `&mut S`.
216+
217+
An alternative approach we could take would be to have the `next` method take `Pin<&mut S>`, rather than `&mut S`. However, this would require pinning even when the type is `Unpin`. The current approach requires pinning only when the type is not `Unpin`. Additionally, if you already have a `Pin<&mut S>`, you can access `.next()` through the implementation described below because `Pin<P>: Unpin`.
218+
219+
At the moment, we do not see many `!Unpin` streams in practice (though there is one in the [futures-intrusive crate](https://github.com/Matthias247/futures-intrusive/blob/master/src/channel/mpmc.rs#L565-L625)). Where they will become important is when we introduce async generators, as discussed in [Future possibilities](future-possibilities).
220+
221+
186222
# Reference-level explanation
187223
[reference-level-explanation]: #reference-level-explanation
188224

@@ -220,7 +256,7 @@ Stream` values without the need to monomorphize the functions that work
220256
with them.
221257

222258
Unfortunately, the use of poll does mean that it is harder to write
223-
stream implementations. The long-term fix for this, discussed in the [Future possibilities][future-possibilities] section, is dedicated [generator syntax].
259+
stream implementations. The long-term fix for this, discussed in the [Future possiblilities](future-possibilities) section, is dedicated [generator syntax].
224260

225261
# Drawbacks
226262
[drawbacks]: #drawbacks
@@ -275,9 +311,6 @@ and to come back to the problem of extending it with combinators.
275311

276312
[outstanding design issues]: https://rust-lang.github.io/wg-async-foundations/design_notes/async_closures.html
277313

278-
Another reason to defer adding combinators is because of the possibility
279-
that some combinators may work best
280-
281314
This path does carry some risk. Adding combinator methods can cause
282315
existing code to stop compiling due to the ambiguities in method
283316
resolution. We have had problems in the past with attempting to migrate
@@ -483,6 +516,16 @@ We may wish to extend the `for` loop so that it works over streams as well.
483516
for elem in stream { ... }
484517
```
485518

519+
One of the complications of using `while let` syntax is the need to pin.
520+
A `for` loop syntax that takes ownership of the stream would be able to
521+
do the pinning for you.
522+
523+
On the other hand, we may not want to make sequential processing "too easy" without also enabling
524+
parallel/concurrent processing, which people frequently want. One challenge is
525+
that parallel processing wouldn't naively permit early returns and other complex
526+
control flow. We could add a `par_stream()` method, similar to
527+
[Rayon's](https://github.com/rayon-rs/rayon) `par_iter()`.
528+
486529
Designing this extension is out of scope for this RFC. However, it could be prototyped using procedural macros today.
487530

488531
## "Lending" streams
@@ -589,6 +632,14 @@ Resolving this would require either an explicit “wrapper” step or else some
589632

590633
It should be noted that the same applies to Iterator, it is not unique to Stream.
591634

635+
We may eventually want a super trait relationship available in the Rust language
636+
637+
```rust
638+
trait Stream: LendingStream
639+
```
640+
641+
This would allow us to leverage `default impl`.
642+
592643
These use cases for lending/non-lending streams need more thought, which is part of the reason it
593644
is out of the scope of this particular RFC.
594645

@@ -634,4 +685,75 @@ If we introduce `-> Stream` first, we will have to permit `LendingStream` in the
634685
Additionally, if we introduce `LendingStream` later, we'll have to figure out how
635686
to convert a `LendingStream` into a `Stream` seamlessly.
636687

688+
### Differences between Iterator generators and Async generators
689+
690+
We want `Stream` and `Iterator` to work as analogously as possible, including when used with generators. However, in the current design, there is a crucial difference between the two.
691+
692+
Consider Iterator's core `next` method:
693+
694+
```rust
695+
pub trait Iterator {
696+
type Item;
697+
698+
fn next(&mut self) -> Option<Self::Item>;
699+
}
700+
```
701+
And then compare it to the proposed Stream `next` method:
702+
703+
```rust
704+
pub trait Stream {
705+
type Item;
706+
707+
fn next(&mut self) -> Next<'_, Self>
708+
where
709+
Self: Unpin;
710+
}
711+
```
712+
713+
Iterator does not require pinning its core next method. In order for a `gen fn` to operate with the Iterator ecosystem, there must be some kind of initial pinning step that converts its result into an iterator. This will be tricky, since you can't return a pinned value except by boxing.
714+
715+
The general shape will be:
716+
717+
```rust
718+
gen_fn().pin_somehow().adapter1().adapter2()
719+
```
720+
721+
With streams, the core interface _is_ pinned, so pinning occurs at the last moment.
722+
723+
The general shape would be
724+
725+
```rust
726+
async_gen_fn().adapter1().adapter2().pin_somehow()
727+
```
728+
729+
Pinning at the end, like with a stream, lets you build and return those adapters and then apply pinning at the end. This may be the more efficient setup and implies that, in order to have a `gen fn` that produces iterators, we will need to potentially disallow borrowing yields or implement some kind of `PinnedIterator` trait that can be "adapted" into an iterator by pinning.
730+
731+
For example:
732+
733+
```rust
734+
trait PinIterator {
735+
type Item;
736+
fn next(self: Pin<&mut Self>) -> Self::Item;
737+
// combinators can go here (duplicating Iterator for the most part)
738+
}
739+
impl<I: PinIterator, P: Deref<Target = I> + DerefMut> Iterator for Pin<P> {
740+
type Item = <I as PinIterator>::Item;
741+
fn next(&mut self) -> Self::Item { self.as_mut().next() }
742+
}
743+
744+
// this would be nice.. but would lead to name resolution ambiguity for our combinators 😬
745+
default impl<T: Iterator> PinIterator for T { .. }
746+
```
747+
Pinning also applies to the design of AsyncRead/AsyncWrite, which currently uses Pin even through there is no clear plan to make them implemented with generator type syntax. The asyncification of a signature is currently understood as pinned receiver + context arg + return poll.
748+
749+
### Yielding
750+
751+
It would be useful to be able to yield from inside a for loop, as long as the for loop is
752+
over a borrowed input and not something owned by the stack frame.
753+
754+
In the spirit of experimentation, boats has written the [propane](https://github.com/withoutboats/propane)
755+
crate. This crate includes a `#[propane] fn` that changes the function signature
756+
to return `impl Iterator` and lets you `yield`. The non-async version uses
757+
`static generator` that is currently in nightly only.
758+
637759
Further designing generator functions is out of the scope of this RFC.

0 commit comments

Comments
 (0)