Skip to content

Commit 263dbc6

Browse files
bittranceAnders Qvist
andauthored
Implement TryStreamExt::try_take_while (#2212)
Co-authored-by: Anders Qvist <[email protected]>
1 parent cc01570 commit 263dbc6

File tree

4 files changed

+168
-2
lines changed

4 files changed

+168
-2
lines changed

futures-util/src/stream/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ mod try_stream;
4545
pub use self::try_stream::{
4646
try_unfold, AndThen, ErrInto, InspectErr, InspectOk, IntoStream, MapErr, MapOk, OrElse,
4747
TryCollect, TryConcat, TryFilter, TryFilterMap, TryFlatten, TryFold, TryForEach, TryNext,
48-
TrySkipWhile, TryStreamExt, TryUnfold,
48+
TrySkipWhile, TryStreamExt, TryTakeWhile, TryUnfold,
4949
};
5050

5151
#[cfg(feature = "io")]

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ mod try_skip_while;
103103
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
104104
pub use self::try_skip_while::TrySkipWhile;
105105

106+
mod try_take_while;
107+
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
108+
pub use self::try_take_while::TryTakeWhile;
109+
106110
cfg_target_has_atomic! {
107111
#[cfg(feature = "alloc")]
108112
mod try_buffer_unordered;
@@ -432,6 +436,36 @@ pub trait TryStreamExt: TryStream {
432436
TrySkipWhile::new(self, f)
433437
}
434438

439+
/// Take elements on this stream while the provided asynchronous predicate
440+
/// resolves to `true`.
441+
///
442+
/// This function is similar to
443+
/// [`StreamExt::take_while`](crate::stream::StreamExt::take_while) but exits
444+
/// early if an error occurs.
445+
///
446+
/// # Examples
447+
///
448+
/// ```
449+
/// # futures::executor::block_on(async {
450+
/// use futures::future;
451+
/// use futures::stream::{self, TryStreamExt};
452+
///
453+
/// let stream = stream::iter(vec![Ok::<i32, i32>(1), Ok(2), Ok(3), Ok(2)]);
454+
/// let stream = stream.try_take_while(|x| future::ready(Ok(*x < 3)));
455+
///
456+
/// let output: Result<Vec<i32>, i32> = stream.try_collect().await;
457+
/// assert_eq!(output, Ok(vec![1, 2]));
458+
/// # })
459+
/// ```
460+
fn try_take_while<Fut, F>(self, f: F) -> TryTakeWhile<Self, Fut, F>
461+
where
462+
F: FnMut(&Self::Ok) -> Fut,
463+
Fut: TryFuture<Ok = bool, Error = Self::Error>,
464+
Self: Sized,
465+
{
466+
TryTakeWhile::new(self, f)
467+
}
468+
435469
/// Attempts to run this stream to completion, executing the provided asynchronous
436470
/// closure for each element on the stream concurrently as elements become
437471
/// available, exiting as soon as an error occurs.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use core::fmt;
2+
use core::pin::Pin;
3+
use futures_core::future::TryFuture;
4+
use futures_core::stream::{FusedStream, Stream, TryStream};
5+
use futures_core::task::{Context, Poll};
6+
#[cfg(feature = "sink")]
7+
use futures_sink::Sink;
8+
use pin_project::pin_project;
9+
10+
/// Stream for the [`try_take_while`](super::TryStreamExt::try_take_while)
11+
/// method.
12+
#[pin_project]
13+
#[must_use = "streams do nothing unless polled"]
14+
pub struct TryTakeWhile<St, Fut, F>
15+
where
16+
St: TryStream,
17+
{
18+
#[pin]
19+
stream: St,
20+
f: F,
21+
#[pin]
22+
pending_fut: Option<Fut>,
23+
pending_item: Option<St::Ok>,
24+
done_taking: bool,
25+
}
26+
27+
impl<St, Fut, F> fmt::Debug for TryTakeWhile<St, Fut, F>
28+
where
29+
St: TryStream + fmt::Debug,
30+
St::Ok: fmt::Debug,
31+
Fut: fmt::Debug,
32+
{
33+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34+
f.debug_struct("TryTakeWhile")
35+
.field("stream", &self.stream)
36+
.field("pending_fut", &self.pending_fut)
37+
.field("pending_item", &self.pending_item)
38+
.field("done_taking", &self.done_taking)
39+
.finish()
40+
}
41+
}
42+
43+
impl<St, Fut, F> TryTakeWhile<St, Fut, F>
44+
where
45+
St: TryStream,
46+
F: FnMut(&St::Ok) -> Fut,
47+
Fut: TryFuture<Ok = bool, Error = St::Error>,
48+
{
49+
pub(super) fn new(stream: St, f: F) -> TryTakeWhile<St, Fut, F> {
50+
TryTakeWhile {
51+
stream,
52+
f,
53+
pending_fut: None,
54+
pending_item: None,
55+
done_taking: false,
56+
}
57+
}
58+
59+
delegate_access_inner!(stream, St, ());
60+
}
61+
62+
impl<St, Fut, F> Stream for TryTakeWhile<St, Fut, F>
63+
where
64+
St: TryStream,
65+
F: FnMut(&St::Ok) -> Fut,
66+
Fut: TryFuture<Ok = bool, Error = St::Error>,
67+
{
68+
type Item = Result<St::Ok, St::Error>;
69+
70+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
71+
let mut this = self.project();
72+
73+
if *this.done_taking {
74+
return Poll::Ready(None);
75+
}
76+
77+
Poll::Ready(loop {
78+
if let Some(fut) = this.pending_fut.as_mut().as_pin_mut() {
79+
let take = ready!(fut.try_poll(cx)?);
80+
let item = this.pending_item.take();
81+
this.pending_fut.set(None);
82+
if take {
83+
break item.map(Ok);
84+
} else {
85+
*this.done_taking = true;
86+
break None;
87+
}
88+
} else if let Some(item) = ready!(this.stream.as_mut().try_poll_next(cx)?) {
89+
this.pending_fut.set(Some((this.f)(&item)));
90+
*this.pending_item = Some(item);
91+
} else {
92+
break None;
93+
}
94+
})
95+
}
96+
97+
fn size_hint(&self) -> (usize, Option<usize>) {
98+
if self.done_taking {
99+
return (0, Some(0));
100+
}
101+
102+
let pending_len = if self.pending_item.is_some() { 1 } else { 0 };
103+
let (_, upper) = self.stream.size_hint();
104+
let upper = match upper {
105+
Some(x) => x.checked_add(pending_len),
106+
None => None,
107+
};
108+
(0, upper) // can't know a lower bound, due to the predicate
109+
}
110+
}
111+
112+
impl<St, Fut, F> FusedStream for TryTakeWhile<St, Fut, F>
113+
where
114+
St: TryStream + FusedStream,
115+
F: FnMut(&St::Ok) -> Fut,
116+
Fut: TryFuture<Ok = bool, Error = St::Error>,
117+
{
118+
fn is_terminated(&self) -> bool {
119+
self.done_taking || self.pending_item.is_none() && self.stream.is_terminated()
120+
}
121+
}
122+
123+
// Forwarding impl of Sink from the underlying stream
124+
#[cfg(feature = "sink")]
125+
impl<S, Fut, F, Item, E> Sink<Item> for TryTakeWhile<S, Fut, F>
126+
where
127+
S: TryStream + Sink<Item, Error = E>,
128+
{
129+
type Error = E;
130+
131+
delegate_sink!(stream, Item);
132+
}

futures/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ pub mod stream {
465465
AndThen, ErrInto, MapOk, MapErr, OrElse,
466466
InspectOk, InspectErr,
467467
TryNext, TryForEach, TryFilter, TryFilterMap, TryFlatten,
468-
TryCollect, TryConcat, TryFold, TrySkipWhile,
468+
TryCollect, TryConcat, TryFold, TrySkipWhile, TryTakeWhile,
469469
IntoStream,
470470
};
471471

0 commit comments

Comments
 (0)