Skip to content

Commit 5f7a443

Browse files
bors[bot]tirr-c
andauthored
Merge #192
192: Add Stream::scan r=stjepang a=tirr-c Ref #129. The mapper function `f` is synchronous and returns bare `Option<B>`. Asynchronous `f` seems tricky to implement right. It requires the wrapper to be self-referential, as a reference to internal state may be captured by the returned future. Co-authored-by: Wonwoo Choi <[email protected]>
2 parents ff9437d + 91e61cf commit 5f7a443

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed

src/stream/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub use double_ended_stream::DoubleEndedStream;
2525
pub use empty::{empty, Empty};
2626
pub use once::{once, Once};
2727
pub use repeat::{repeat, Repeat};
28-
pub use stream::{Stream, Take};
28+
pub use stream::{Scan, Stream, Take};
2929

3030
mod double_ended_stream;
3131
mod empty;

src/stream/stream/mod.rs

+45
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ mod find_map;
2929
mod min_by;
3030
mod next;
3131
mod nth;
32+
mod scan;
3233
mod take;
3334

35+
pub use scan::Scan;
3436
pub use take::Take;
3537

3638
use all::AllFuture;
@@ -501,6 +503,49 @@ pub trait Stream {
501503
f,
502504
}
503505
}
506+
507+
/// A stream adaptor similar to [`fold`] that holds internal state and produces a new stream.
508+
///
509+
/// [`fold`]: #method.fold
510+
///
511+
/// `scan()` takes two arguments: an initial value which seeds the internal state, and a
512+
/// closure with two arguments, the first being a mutable reference to the internal state and
513+
/// the second a stream element. The closure can assign to the internal state to share state
514+
/// between iterations.
515+
///
516+
/// On iteration, the closure will be applied to each element of the stream and the return
517+
/// value from the closure, an `Option`, is yielded by the stream.
518+
///
519+
/// ## Examples
520+
///
521+
/// ```
522+
/// # fn main() { async_std::task::block_on(async {
523+
/// #
524+
/// use std::collections::VecDeque;
525+
/// use async_std::stream::Stream;
526+
///
527+
/// let s: VecDeque<isize> = vec![1, 2, 3].into_iter().collect();
528+
/// let mut s = s.scan(1, |state, x| {
529+
/// *state = *state * x;
530+
/// Some(-*state)
531+
/// });
532+
///
533+
/// assert_eq!(s.next().await, Some(-1));
534+
/// assert_eq!(s.next().await, Some(-2));
535+
/// assert_eq!(s.next().await, Some(-6));
536+
/// assert_eq!(s.next().await, None);
537+
/// #
538+
/// # }) }
539+
/// ```
540+
#[inline]
541+
fn scan<St, B, F>(self, initial_state: St, f: F) -> Scan<Self, St, F>
542+
where
543+
Self: Sized,
544+
St: Unpin,
545+
F: Unpin + FnMut(&mut St, Self::Item) -> Option<B>,
546+
{
547+
Scan::new(self, initial_state, f)
548+
}
504549
}
505550

506551
impl<T: futures_core::stream::Stream + Unpin + ?Sized> Stream for T {

src/stream/stream/scan.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use crate::task::{Context, Poll};
2+
3+
use std::pin::Pin;
4+
5+
/// A stream to maintain state while polling another stream.
6+
#[derive(Debug)]
7+
pub struct Scan<S, St, F> {
8+
stream: S,
9+
state_f: (St, F),
10+
}
11+
12+
impl<S, St, F> Scan<S, St, F> {
13+
pub(crate) fn new(stream: S, initial_state: St, f: F) -> Self {
14+
Self {
15+
stream,
16+
state_f: (initial_state, f),
17+
}
18+
}
19+
20+
pin_utils::unsafe_pinned!(stream: S);
21+
pin_utils::unsafe_unpinned!(state_f: (St, F));
22+
}
23+
24+
impl<S: Unpin, St, F> Unpin for Scan<S, St, F> {}
25+
26+
impl<S, St, F, B> futures_core::stream::Stream for Scan<S, St, F>
27+
where
28+
S: futures_core::stream::Stream,
29+
F: FnMut(&mut St, S::Item) -> Option<B>,
30+
{
31+
type Item = B;
32+
33+
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<B>> {
34+
let poll_result = self.as_mut().stream().poll_next(cx);
35+
poll_result.map(|item| {
36+
item.and_then(|item| {
37+
let (state, f) = self.as_mut().state_f();
38+
f(state, item)
39+
})
40+
})
41+
}
42+
}

0 commit comments

Comments
 (0)