-
Notifications
You must be signed in to change notification settings - Fork 0
7971: stream: add StreamExt::filter_map_async
#78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,6 +16,9 @@ use collect::{Collect, FromStream}; | |||||||||||||||||||||||||
| mod filter; | ||||||||||||||||||||||||||
| pub use filter::Filter; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| mod filter_map_async; | ||||||||||||||||||||||||||
| pub use filter_map_async::FilterMapAsync; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| mod filter_map; | ||||||||||||||||||||||||||
| pub use filter_map::FilterMap; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -479,6 +482,53 @@ pub trait StreamExt: Stream { | |||||||||||||||||||||||||
| FilterMap::new(self, f) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /// Filters the values produced by this stream asynchronously while | ||||||||||||||||||||||||||
| /// simultaneously mapping them to a different type according to the | ||||||||||||||||||||||||||
| /// provided async closure. | ||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// The provided closure is executed over all elements of this stream as | ||||||||||||||||||||||||||
| /// they are made available, and the returned future is executed. Only one | ||||||||||||||||||||||||||
| /// future is executed at the time. If the returned future resolves to | ||||||||||||||||||||||||||
| /// [`Some(item)`](Some) then the stream will yield the value `item`, but if | ||||||||||||||||||||||||||
| /// it resolves to [`None`], then the value will be skipped. | ||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// Note that this function consumes the stream passed into it and returns a | ||||||||||||||||||||||||||
| /// wrapped version of it, similar to [`Iterator::filter_map`] method in the | ||||||||||||||||||||||||||
| /// standard library. | ||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// Be aware that if the future is not `Unpin`, then neither is the `Stream` | ||||||||||||||||||||||||||
| /// returned by this method. To handle this, you can use `tokio::pin!` as in | ||||||||||||||||||||||||||
| /// the example below or put the stream in a `Box` with `Box::pin(stream)`. | ||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// # Examples | ||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||
| /// # #[tokio::main(flavor = "current_thread")] | ||||||||||||||||||||||||||
| /// # async fn main() { | ||||||||||||||||||||||||||
| /// use tokio_stream::{self as stream, StreamExt}; | ||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// let stream = stream::iter(0..=7); | ||||||||||||||||||||||||||
| /// let odds = stream.filter_map_async(async |x| { | ||||||||||||||||||||||||||
| /// if x % 2 == 0 { Some(x + 1) } else { None } | ||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doc example uses async closure syntax incompatible with MSRVMedium Severity The doc example uses
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:valid-but-wont-fix; category:bug; feedback: The Bugbot AI reviewer is correct but since this is a doctest it is executed only with the nightly toolchain, so the build passes! Most of the Tokio users will use a recent toolchain so the example will work as is. The users who has to use older toolchain than 1.79 could easily adapt it |
||||||||||||||||||||||||||
| /// }); | ||||||||||||||||||||||||||
|
Comment on lines
+510
to
+512
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:valid-but-wont-fix; category:bug; feedback: The Gemini AI reviewer is correct but since this is a doctest it is executed only with the nightly toolchain, so the build passes! Most of the Tokio users will use a recent toolchain so the example will work as is. The users who has to use older toolchain than 1.79 could easily adapt it
Comment on lines
+510
to
+512
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== rust-version declarations =="
fd '^Cargo.toml$' -a | while read -r f; do
if rg -n '^\s*rust-version\s*=' "$f" >/dev/null; then
echo "-- $f"
rg -n '^\s*(name|version|rust-version)\s*=' "$f"
fi
done
echo
echo "== async-closure syntax occurrences in tokio-stream =="
rg -nP '\basync\s*\|' tokio-stream/srcRepository: martin-augment/tokio Length of output: 835 🌐 Web query:
💡 Result: Rust’s async closure syntax (e.g., Before 1.85, it was only available on nightly behind Sources: Use MSRV-compatible closure syntax in the doctest example. The Suggested fix-/// let odds = stream.filter_map_async(async |x| {
+/// let odds = stream.filter_map_async(|x| async move {
/// if x % 2 == 0 { Some(x + 1) } else { None }
/// });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:valid-but-wont-fix; category:bug; feedback: The CodeRabbit AI reviewer is correct but since this is a doctest it is executed only with the nightly toolchain, so the build passes! Most of the Tokio users will use a recent toolchain so the example will work as is. The users who has to use older toolchain than 1.79 could easily adapt it |
||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// tokio::pin!(odds); | ||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// assert_eq!(Some(1), odds.next().await); | ||||||||||||||||||||||||||
| /// assert_eq!(Some(3), odds.next().await); | ||||||||||||||||||||||||||
| /// assert_eq!(Some(5), odds.next().await); | ||||||||||||||||||||||||||
| /// assert_eq!(Some(7), odds.next().await); | ||||||||||||||||||||||||||
| /// assert_eq!(None, odds.next().await); | ||||||||||||||||||||||||||
| /// # } | ||||||||||||||||||||||||||
| /// ``` | ||||||||||||||||||||||||||
| fn filter_map_async<T, F, Fut>(self, f: F) -> FilterMapAsync<Self, Fut, F> | ||||||||||||||||||||||||||
| where | ||||||||||||||||||||||||||
| F: FnMut(Self::Item) -> Fut, | ||||||||||||||||||||||||||
| Fut: Future<Output = Option<T>>, | ||||||||||||||||||||||||||
| Self: Sized, | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| FilterMapAsync::new(self, f) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /// Creates a stream which ends after the first `None`. | ||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// After a stream returns `None`, behavior is undefined. Future calls to | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| use crate::Stream; | ||
|
|
||
| use core::fmt; | ||
| use core::future::Future; | ||
| use core::pin::Pin; | ||
| use core::task::{ready, Context, Poll}; | ||
| use pin_project_lite::pin_project; | ||
|
|
||
| pin_project! { | ||
| /// Stream for the [`filter_map_async`](super::StreamExt::filter_map_async) method. | ||
| #[must_use = "streams do nothing unless polled"] | ||
| pub struct FilterMapAsync<St, Fut, F> { | ||
| #[pin] | ||
| stream: St, | ||
| #[pin] | ||
| future: Option<Fut>, | ||
| f: F, | ||
| } | ||
| } | ||
|
|
||
| impl<St, Fut, F> fmt::Debug for FilterMapAsync<St, Fut, F> | ||
| where | ||
| St: fmt::Debug, | ||
| { | ||
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| f.debug_struct("FilterMapAsync") | ||
| .field("stream", &self.stream) | ||
| .finish() | ||
| } | ||
| } | ||
|
|
||
| impl<St, Fut, F> FilterMapAsync<St, Fut, F> { | ||
| pub(super) fn new(stream: St, f: F) -> Self { | ||
| FilterMapAsync { | ||
| stream, | ||
| future: None, | ||
| f, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<T, St, F, Fut> Stream for FilterMapAsync<St, Fut, F> | ||
| where | ||
| St: Stream, | ||
| Fut: Future<Output = Option<T>>, | ||
| F: FnMut(St::Item) -> Fut, | ||
| { | ||
| type Item = T; | ||
|
|
||
| fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> { | ||
| let mut me = self.project(); | ||
|
|
||
| loop { | ||
| if let Some(future) = me.future.as_mut().as_pin_mut() { | ||
| if let Some(item) = ready!(future.poll(cx)) { | ||
| me.future.set(None); | ||
| return Poll::Ready(Some(item)); | ||
| } else { | ||
| me.future.set(None); | ||
| } | ||
|
Comment on lines
+55
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The call to let result = ready!(future.poll(cx));
me.future.set(None);
if let Some(item) = result {
return Poll::Ready(Some(item));
}
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:good-to-have; category:bug; feedback: The Gemini AI reviewer is correct! The same statement is executed in both 'if' and 'else' bodies, so it could be extracted before the |
||
| } | ||
|
|
||
| match ready!(me.stream.as_mut().poll_next(cx)) { | ||
| Some(item) => { | ||
| me.future.set(Some((me.f)(item))); | ||
| } | ||
| None => return Poll::Ready(None), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn size_hint(&self) -> (usize, Option<usize>) { | ||
| (0, self.stream.size_hint().1) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Severity: medium 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value:useful; category:bug; feedback: The Augment AI reviewer is correct! The logic should take into account also the current future as the others combinators do. Since this is hint it does not need to be exact but it would be nice to use a more correct value if it is not too complex to calculate it. |
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example uses
async |x| { ... }syntax, which won’t compile on Tokio’s MSRV (Rust 1.71). This can break rustdoc/doctests forfilter_map_async’s docs.Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
value:valid-but-wont-fix; category:bug; feedback: The Augment AI reviewer is correct but since this is a doctest it is executed only with the nightly toolchain, so the build passes! Most of the Tokio users will use a recent toolchain so the example will work as is. The users who has to use older toolchain than 1.79 could easily adapt it