Skip to content

Commit c0e9368

Browse files
ibraheemdevmukund
and
mukund
authored
Add TryStreamExt::try_forward, remove TryStream bound from StreamExt::forward (#2469)
Co-authored-by: mukund <[email protected]>
1 parent 37dfb05 commit c0e9368

File tree

10 files changed

+165
-34
lines changed

10 files changed

+165
-34
lines changed

futures-util/src/io/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ pub trait AsyncWriteExt: AsyncWrite {
575575
/// use futures::io::AsyncWriteExt;
576576
/// use futures::stream::{self, StreamExt};
577577
///
578-
/// let stream = stream::iter(vec![Ok([1, 2, 3]), Ok([4, 5, 6])]);
578+
/// let stream = stream::iter(vec![[1, 2, 3], [4, 5, 6]]);
579579
///
580580
/// let mut writer = vec![];
581581
///

futures-util/src/stream/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ pub use self::try_stream::IntoAsyncRead;
6363
#[cfg(feature = "alloc")]
6464
pub use self::try_stream::{TryBufferUnordered, TryBuffered};
6565

66+
#[cfg(feature = "sink")]
67+
#[cfg_attr(docsrs, doc(cfg(feature = "sink")))]
68+
pub use self::try_stream::TryForward;
69+
6670
#[cfg(feature = "alloc")]
6771
pub use self::try_stream::{TryChunks, TryChunksError};
6872

futures-util/src/stream/stream/forward.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl<St, Si, Item> Forward<St, Si, Item> {
3030
impl<St, Si, Item, E> FusedFuture for Forward<St, Si, Item>
3131
where
3232
Si: Sink<Item, Error = E>,
33-
St: Stream<Item = Result<Item, E>>,
33+
St: Stream<Item = Item>,
3434
{
3535
fn is_terminated(&self) -> bool {
3636
self.sink.is_none()
@@ -40,7 +40,7 @@ where
4040
impl<St, Si, Item, E> Future for Forward<St, Si, Item>
4141
where
4242
Si: Sink<Item, Error = E>,
43-
St: Stream<Item = Result<Item, E>>,
43+
St: Stream<Item = Item>,
4444
{
4545
type Output = Result<(), E>;
4646

@@ -56,7 +56,7 @@ where
5656
si.as_mut().start_send(buffered_item.take().unwrap())?;
5757
}
5858

59-
match stream.as_mut().poll_next(cx)? {
59+
match stream.as_mut().poll_next(cx) {
6060
Poll::Ready(Some(item)) => {
6161
*buffered_item = Some(item);
6262
}

futures-util/src/stream/stream/fuse.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pin_project! {
1818
}
1919

2020
impl<St> Fuse<St> {
21-
pub(super) fn new(stream: St) -> Self {
21+
pub(crate) fn new(stream: St) -> Self {
2222
Self { stream, done: false }
2323
}
2424

futures-util/src/stream/stream/mod.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ use alloc::boxed::Box;
1010
#[cfg(feature = "alloc")]
1111
use alloc::vec::Vec;
1212
use core::pin::Pin;
13-
#[cfg(feature = "sink")]
14-
use futures_core::stream::TryStream;
1513
#[cfg(feature = "alloc")]
1614
use futures_core::stream::{BoxStream, LocalBoxStream};
1715
use futures_core::{
@@ -86,9 +84,9 @@ delegate_all!(
8684
/// Future for the [`forward`](super::StreamExt::forward) method.
8785
#[cfg_attr(docsrs, doc(cfg(feature = "sink")))]
8886
Forward<St, Si>(
89-
forward::Forward<St, Si, St::Ok>
87+
forward::Forward<St, Si, St::Item>
9088
): Debug + Future + FusedFuture + New[|x: St, y: Si| forward::Forward::new(x, y)]
91-
where St: TryStream
89+
where St: Stream
9290
);
9391

9492
mod for_each;
@@ -1551,19 +1549,15 @@ pub trait StreamExt: Stream {
15511549
/// the sink is closed. Note that neither the original stream nor provided
15521550
/// sink will be output by this future. Pass the sink by `Pin<&mut S>`
15531551
/// (for example, via `forward(&mut sink)` inside an `async` fn/block) in
1554-
/// order to preserve access to the `Sink`. If the stream produces an error,
1555-
/// that error will be returned by this future without flushing/closing the sink.
1552+
/// order to preserve access to the `Sink`.
15561553
#[cfg(feature = "sink")]
15571554
#[cfg_attr(docsrs, doc(cfg(feature = "sink")))]
15581555
fn forward<S>(self, sink: S) -> Forward<Self, S>
15591556
where
1560-
S: Sink<Self::Ok, Error = Self::Error>,
1561-
Self: TryStream + Sized,
1562-
// Self: TryStream + Sized + Stream<Item = Result<<Self as TryStream>::Ok, <Self as TryStream>::Error>>,
1557+
S: Sink<Self::Item>,
1558+
Self: Sized,
15631559
{
1564-
// TODO: type mismatch resolving `<Self as futures_core::Stream>::Item == std::result::Result<<Self as futures_core::TryStream>::Ok, <Self as futures_core::TryStream>::Error>`
1565-
// assert_future::<Result<(), Self::Error>, _>(Forward::new(self, sink))
1566-
Forward::new(self, sink)
1560+
assert_future::<Result<(), S::Error>, _>(Forward::new(self, sink))
15671561
}
15681562

15691563
/// Splits this `Stream + Sink` object into separate `Sink` and `Stream`

futures-util/src/stream/try_stream/mod.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ use crate::fns::{
1010
IntoFn, MapErrFn, MapOkFn,
1111
};
1212
use crate::future::assert_future;
13-
use crate::stream::assert_stream;
14-
use crate::stream::{Inspect, Map};
13+
use crate::stream::{assert_stream, Inspect, Map};
1514
#[cfg(feature = "alloc")]
1615
use alloc::vec::Vec;
1716
use core::pin::Pin;
@@ -20,6 +19,8 @@ use futures_core::{
2019
stream::TryStream,
2120
task::{Context, Poll},
2221
};
22+
#[cfg(feature = "sink")]
23+
use futures_sink::Sink;
2324

2425
mod and_then;
2526
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
@@ -76,6 +77,19 @@ mod try_filter;
7677
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
7778
pub use self::try_filter::TryFilter;
7879

80+
#[cfg(feature = "sink")]
81+
mod try_forward;
82+
83+
#[cfg(feature = "sink")]
84+
delegate_all!(
85+
/// Future for the [`try_forward`](super::TryStreamExt::try_forward) method.
86+
#[cfg_attr(docsrs, doc(cfg(feature = "sink")))]
87+
TryForward<St, Si>(
88+
try_forward::TryForward<St, Si, St::Ok>
89+
): Debug + Future + FusedFuture + New[|x: St, y: Si| try_forward::TryForward::new(x, y)]
90+
where St: TryStream
91+
);
92+
7993
mod try_filter_map;
8094
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
8195
pub use self::try_filter_map::TryFilterMap;
@@ -287,6 +301,27 @@ pub trait TryStreamExt: TryStream {
287301
assert_stream::<Result<Self::Ok, Fut::Error>, _>(OrElse::new(self, f))
288302
}
289303

304+
/// A future that completes after the given stream has been fully processed
305+
/// into the sink and the sink has been flushed and closed.
306+
///
307+
/// This future will drive the stream to keep producing items until it is
308+
/// exhausted, sending each item to the sink. It will complete once the
309+
/// stream is exhausted, the sink has received and flushed all items, and
310+
/// the sink is closed. Note that neither the original stream nor provided
311+
/// sink will be output by this future. Pass the sink by `Pin<&mut S>`
312+
/// (for example, via `try_forward(&mut sink)` inside an `async` fn/block) in
313+
/// order to preserve access to the `Sink`. If the stream produces an error,
314+
/// that error will be returned by this future without flushing/closing the sink.
315+
#[cfg(feature = "sink")]
316+
#[cfg_attr(docsrs, doc(cfg(feature = "sink")))]
317+
fn try_forward<S>(self, sink: S) -> TryForward<Self, S>
318+
where
319+
S: Sink<Self::Ok, Error = Self::Error>,
320+
Self: Sized,
321+
{
322+
assert_future::<Result<(), Self::Error>, _>(TryForward::new(self, sink))
323+
}
324+
290325
/// Do something with the success value of this stream, afterwards passing
291326
/// it on.
292327
///
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use crate::stream::{Fuse, IntoStream, Stream, TryStream};
2+
use core::pin::Pin;
3+
use futures_core::future::{FusedFuture, Future};
4+
use futures_core::ready;
5+
use futures_core::task::{Context, Poll};
6+
use futures_sink::Sink;
7+
use pin_project_lite::pin_project;
8+
9+
pin_project! {
10+
/// Future for the [`try_forward`](super::TryStreamExt::try_forward) method.
11+
#[project = TryForwardProj]
12+
#[derive(Debug)]
13+
#[must_use = "futures do nothing unless you `.await` or poll them"]
14+
pub struct TryForward<St, Si, Item> {
15+
#[pin]
16+
sink: Option<Si>,
17+
#[pin]
18+
stream: Fuse<IntoStream<St>>,
19+
buffered_item: Option<Item>,
20+
}
21+
}
22+
23+
impl<St, Si, Item> TryForward<St, Si, Item> {
24+
pub(crate) fn new(stream: St, sink: Si) -> Self {
25+
Self { sink: Some(sink), stream: Fuse::new(IntoStream::new(stream)), buffered_item: None }
26+
}
27+
}
28+
29+
impl<St, Si, Item, E> FusedFuture for TryForward<St, Si, Item>
30+
where
31+
Si: Sink<Item, Error = E>,
32+
St: TryStream<Ok = Item, Error = E>,
33+
{
34+
fn is_terminated(&self) -> bool {
35+
self.sink.is_none()
36+
}
37+
}
38+
39+
impl<St, Si, Item, E> Future for TryForward<St, Si, Item>
40+
where
41+
Si: Sink<Item, Error = E>,
42+
St: TryStream<Ok = Item, Error = E>,
43+
{
44+
type Output = Result<(), E>;
45+
46+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
47+
let TryForwardProj { mut sink, mut stream, buffered_item } = self.project();
48+
let mut si = sink.as_mut().as_pin_mut().expect("polled `TryForward` after completion");
49+
50+
loop {
51+
// If we've got an item buffered already, we need to write it to the
52+
// sink before we can do anything else
53+
if buffered_item.is_some() {
54+
ready!(si.as_mut().poll_ready(cx))?;
55+
si.as_mut().start_send(buffered_item.take().unwrap())?;
56+
}
57+
58+
match stream.as_mut().poll_next(cx)? {
59+
Poll::Ready(Some(item)) => {
60+
*buffered_item = Some(item);
61+
}
62+
Poll::Ready(None) => {
63+
ready!(si.poll_close(cx))?;
64+
sink.set(None);
65+
return Poll::Ready(Ok(()));
66+
}
67+
Poll::Pending => {
68+
ready!(si.poll_flush(cx))?;
69+
return Poll::Pending;
70+
}
71+
}
72+
}
73+
}
74+
}

futures/tests/auto_traits.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,17 +1281,29 @@ pub mod stream {
12811281
assert_impl!(ForEachConcurrent<(), PhantomPinned, PhantomPinned>: Unpin);
12821282
assert_not_impl!(ForEachConcurrent<PhantomPinned, (), ()>: Unpin);
12831283

1284-
assert_impl!(Forward<SendTryStream<()>, ()>: Send);
1285-
assert_not_impl!(Forward<SendTryStream, ()>: Send);
1286-
assert_not_impl!(Forward<SendTryStream<()>, *const ()>: Send);
1287-
assert_not_impl!(Forward<LocalTryStream, ()>: Send);
1288-
assert_impl!(Forward<SyncTryStream<()>, ()>: Sync);
1289-
assert_not_impl!(Forward<SyncTryStream, ()>: Sync);
1290-
assert_not_impl!(Forward<SyncTryStream<()>, *const ()>: Sync);
1291-
assert_not_impl!(Forward<LocalTryStream, ()>: Sync);
1292-
assert_impl!(Forward<UnpinTryStream, ()>: Unpin);
1293-
assert_not_impl!(Forward<UnpinTryStream, PhantomPinned>: Unpin);
1294-
assert_not_impl!(Forward<PinnedTryStream, ()>: Unpin);
1284+
assert_impl!(Forward<SendStream<()>, ()>: Send);
1285+
assert_not_impl!(Forward<SendStream, ()>: Send);
1286+
assert_not_impl!(Forward<SendStream<()>, *const ()>: Send);
1287+
assert_not_impl!(Forward<LocalStream, ()>: Send);
1288+
assert_impl!(Forward<SyncStream<()>, ()>: Sync);
1289+
assert_not_impl!(Forward<SyncStream, ()>: Sync);
1290+
assert_not_impl!(Forward<SyncStream<()>, *const ()>: Sync);
1291+
assert_not_impl!(Forward<LocalStream, ()>: Sync);
1292+
assert_impl!(Forward<UnpinStream, ()>: Unpin);
1293+
assert_not_impl!(Forward<UnpinStream, PhantomPinned>: Unpin);
1294+
assert_not_impl!(Forward<PinnedStream, ()>: Unpin);
1295+
1296+
assert_impl!(TryForward<SendTryStream<()>, ()>: Send);
1297+
assert_not_impl!(TryForward<SendTryStream, ()>: Send);
1298+
assert_not_impl!(TryForward<SendTryStream<()>, *const ()>: Send);
1299+
assert_not_impl!(TryForward<LocalTryStream, ()>: Send);
1300+
assert_impl!(TryForward<SyncTryStream<()>, ()>: Sync);
1301+
assert_not_impl!(TryForward<SyncTryStream, ()>: Sync);
1302+
assert_not_impl!(TryForward<SyncTryStream<()>, *const ()>: Sync);
1303+
assert_not_impl!(TryForward<LocalTryStream, ()>: Sync);
1304+
assert_impl!(TryForward<UnpinTryStream, ()>: Unpin);
1305+
assert_not_impl!(TryForward<UnpinTryStream, PhantomPinned>: Unpin);
1306+
assert_not_impl!(TryForward<PinnedTryStream, ()>: Unpin);
12951307

12961308
assert_impl!(Fuse<()>: Send);
12971309
assert_not_impl!(Fuse<*const ()>: Send);

futures/tests/sink_fanout.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn it_works() {
1717
let collect_fut2 = rx2.collect::<Vec<_>>();
1818
let (_, vec1, vec2) = block_on(async move { join!(fwd, collect_fut1, collect_fut2) });
1919

20-
let expected = (0..10).collect::<Vec<_>>();
20+
let expected = (0..10).map(Ok::<_, ()>).collect::<Vec<_>>();
2121

2222
assert_eq!(vec1, expected);
2323
assert_eq!(vec2, expected);

futures/tests_disabled/stream.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,26 @@ fn chunks_panic_on_cap_zero() {
305305
#[test]
306306
fn forward() {
307307
let v = Vec::new();
308-
let v = block_on(iter_ok::<_, Never>(vec![0, 1]).forward(v)).unwrap().1;
308+
let v = block_on(iter(vec![0, 1]).forward(v)).unwrap().1;
309309
assert_eq!(v, vec![0, 1]);
310310

311-
let v = block_on(iter_ok::<_, Never>(vec![2, 3]).forward(v)).unwrap().1;
311+
let v = block_on(iter(vec![2, 3]).forward(v)).unwrap().1;
312+
assert_eq!(v, vec![0, 1, 2, 3]);
313+
314+
assert_done(move || iter(vec![4, 5]).forward(v).map(|(_, s)| s), Ok(vec![0, 1, 2, 3, 4, 5]));
315+
}
316+
317+
#[test]
318+
fn try_forward() {
319+
let v = Vec::new();
320+
let v = block_on(iter_ok::<_, Never>(vec![0, 1]).try_forward(v)).unwrap().1;
321+
assert_eq!(v, vec![0, 1]);
322+
323+
let v = block_on(iter_ok::<_, Never>(vec![2, 3]).try_forward(v)).unwrap().1;
312324
assert_eq!(v, vec![0, 1, 2, 3]);
313325

314326
assert_done(
315-
move || iter_ok::<_, Never>(vec![4, 5]).forward(v).map(|(_, s)| s),
327+
move || iter_ok::<_, Never>(vec![4, 5]).try_forward(v).map(|(_, s)| s),
316328
Ok(vec![0, 1, 2, 3, 4, 5]),
317329
);
318330
}

0 commit comments

Comments
 (0)