stream: add next and map utility fn (#1962)

Introduces `StreamExt` trait. This trait will be used to add utility functions
to make working with streams easier. This patch includes two functions:

* `next`: a future returning the item in the stream.
* `map`: transform each item in the stream.
This commit is contained in:
Artem Vorotnikov
2019-12-18 11:57:22 -08:00
committed by Carl Lerche
parent b0836ece7a
commit 4c645866ef
42 changed files with 295 additions and 57 deletions
+4 -3
View File
@@ -27,10 +27,11 @@
#![warn(rust_2018_idioms)]
use tokio::net::{TcpListener, TcpStream};
use tokio::stream::{Stream, StreamExt};
use tokio::sync::{mpsc, Mutex};
use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
use futures::{SinkExt, Stream, StreamExt};
use futures::SinkExt;
use std::collections::HashMap;
use std::env;
use std::error::Error;
@@ -163,12 +164,12 @@ impl Stream for Peer {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// First poll the `UnboundedReceiver`.
if let Poll::Ready(Some(v)) = self.rx.poll_next_unpin(cx) {
if let Poll::Ready(Some(v)) = Pin::new(&mut self.rx).poll_next(cx) {
return Poll::Ready(Some(Ok(Message::Received(v))));
}
// Secondly poll the `Framed` stream.
let result: Option<_> = futures::ready!(self.lines.poll_next_unpin(cx));
let result: Option<_> = futures::ready!(Pin::new(&mut self.lines).poll_next(cx));
Poll::Ready(match result {
// We've received a message we should broadcast to others.
+8 -5
View File
@@ -55,19 +55,21 @@ async fn main() -> Result<(), Box<dyn Error>> {
mod tcp {
use super::codec;
use futures::{future, Sink, SinkExt, Stream, StreamExt};
use futures::StreamExt;
use futures::{future, Sink, SinkExt};
use std::{error::Error, io, net::SocketAddr};
use tokio::net::TcpStream;
use tokio::stream::Stream;
use tokio_util::codec::{FramedRead, FramedWrite};
pub async fn connect(
addr: &SocketAddr,
stdin: impl Stream<Item = Result<Vec<u8>, io::Error>> + Unpin,
mut stdin: impl Stream<Item = Result<Vec<u8>, io::Error>> + Unpin,
mut stdout: impl Sink<Vec<u8>, Error = io::Error> + Unpin,
) -> Result<(), Box<dyn Error>> {
let mut stream = TcpStream::connect(addr).await?;
let (r, w) = stream.split();
let sink = FramedWrite::new(w, codec::Bytes);
let mut sink = FramedWrite::new(w, codec::Bytes);
let mut stream = FramedRead::new(r, codec::Bytes)
.filter_map(|i| match i {
Ok(i) => future::ready(Some(i)),
@@ -78,7 +80,7 @@ mod tcp {
})
.map(Ok);
match future::join(stdin.forward(sink), stdout.send_all(&mut stream)).await {
match future::join(sink.send_all(&mut stdin), stdout.send_all(&mut stream)).await {
(Err(e), _) | (_, Err(e)) => Err(e.into()),
_ => Ok(()),
}
@@ -88,8 +90,9 @@ mod tcp {
mod udp {
use tokio::net::udp::{RecvHalf, SendHalf};
use tokio::net::UdpSocket;
use tokio::stream::{Stream, StreamExt};
use futures::{future, Sink, SinkExt, Stream, StreamExt};
use futures::{future, Sink, SinkExt};
use std::error::Error;
use std::io;
use std::net::SocketAddr;
+1 -1
View File
@@ -55,9 +55,9 @@
#![warn(rust_2018_idioms)]
use tokio::net::TcpListener;
use tokio::stream::StreamExt;
use tokio_util::codec::{BytesCodec, Decoder};
use futures::StreamExt;
use std::env;
#[tokio::main]
+2 -1
View File
@@ -42,9 +42,10 @@
#![warn(rust_2018_idioms)]
use tokio::net::TcpListener;
use tokio::stream::StreamExt;
use tokio_util::codec::{Framed, LinesCodec};
use futures::{SinkExt, StreamExt};
use futures::SinkExt;
use std::collections::HashMap;
use std::env;
use std::error::Error;
+2 -1
View File
@@ -14,13 +14,14 @@
#![warn(rust_2018_idioms)]
use bytes::BytesMut;
use futures::{SinkExt, StreamExt};
use futures::SinkExt;
use http::{header::HeaderValue, Request, Response, StatusCode};
#[macro_use]
extern crate serde_derive;
use serde_json;
use std::{env, error::Error, fmt, io};
use tokio::net::{TcpListener, TcpStream};
use tokio::stream::StreamExt;
use tokio_util::codec::{Decoder, Encoder, Framed};
#[tokio::main]
+2 -1
View File
@@ -9,12 +9,13 @@
#![warn(rust_2018_idioms)]
use tokio::net::UdpSocket;
use tokio::stream::StreamExt;
use tokio::{io, time};
use tokio_util::codec::BytesCodec;
use tokio_util::udp::UdpFramed;
use bytes::Bytes;
use futures::{FutureExt, SinkExt, StreamExt};
use futures::{FutureExt, SinkExt};
use std::env;
use std::error::Error;
use std::net::SocketAddr;
+1 -1
View File
@@ -20,7 +20,7 @@ Testing utilities for Tokio- and futures-based code
categories = ["asynchronous", "testing"]
[dependencies]
tokio = { version = "0.2.0", path = "../tokio", features = ["rt-core", "sync", "time", "test-util"] }
tokio = { version = "0.2.0", path = "../tokio", features = ["rt-core", "stream", "sync", "time", "test-util"] }
bytes = "0.5.0"
futures-core = "0.3.0"
+2 -1
View File
@@ -2,7 +2,6 @@
#![allow(clippy::mutex_atomic)]
use futures_core::Stream;
use std::future::Future;
use std::mem;
use std::ops;
@@ -10,6 +9,8 @@ use std::pin::Pin;
use std::sync::{Arc, Condvar, Mutex};
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use tokio::stream::Stream;
/// TOOD: dox
pub fn spawn<T>(task: T) -> Spawn<T> {
Spawn {
+1 -1
View File
@@ -3,7 +3,6 @@
use cfg_if::cfg_if;
use env_logger;
use futures::join;
use futures::stream::StreamExt;
use native_tls;
use native_tls::{Identity, TlsAcceptor, TlsConnector};
use std::io::Write;
@@ -12,6 +11,7 @@ use std::process::Command;
use std::ptr;
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt, Error, ErrorKind};
use tokio::net::{TcpListener, TcpStream};
use tokio::stream::StreamExt;
use tokio_tls;
macro_rules! t {
+1 -1
View File
@@ -26,7 +26,7 @@ default = []
# Shorthand for enabling everything
full = ["codec", "udp"]
codec = []
codec = ["tokio/stream"]
udp = ["tokio/udp"]
[dependencies]
+1 -2
View File
@@ -3,10 +3,9 @@ use crate::codec::encoder::Encoder;
use crate::codec::framed_read::{framed_read2, framed_read2_with_buffer, FramedRead2};
use crate::codec::framed_write::{framed_write2, framed_write2_with_buffer, FramedWrite2};
use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite};
use tokio::{io::{AsyncBufRead, AsyncRead, AsyncWrite}, stream::Stream};
use bytes::BytesMut;
use futures_core::Stream;
use futures_sink::Sink;
use pin_project_lite::pin_project;
use std::fmt;
+1 -2
View File
@@ -1,10 +1,9 @@
use crate::codec::framed::{Fuse, ProjectFuse};
use crate::codec::Decoder;
use tokio::io::AsyncRead;
use tokio::{io::AsyncRead, stream::Stream};
use bytes::BytesMut;
use futures_core::Stream;
use futures_sink::Sink;
use log::trace;
use pin_project_lite::pin_project;
+2 -2
View File
@@ -2,10 +2,10 @@ use crate::codec::decoder::Decoder;
use crate::codec::encoder::Encoder;
use crate::codec::framed::{Fuse, ProjectFuse};
use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite};
use tokio::{io::{AsyncBufRead, AsyncRead, AsyncWrite}, stream::Stream};
use bytes::BytesMut;
use futures_core::{ready, Stream};
use futures_core::ready;
use futures_sink::Sink;
use log::trace;
use pin_project_lite::pin_project;
+1 -1
View File
@@ -6,8 +6,8 @@
//!
//! [`AsyncRead`]: https://docs.rs/tokio/*/tokio/io/trait.AsyncRead.html
//! [`AsyncWrite`]: https://docs.rs/tokio/*/tokio/io/trait.AsyncWrite.html
//! [`Stream`]: https://docs.rs/tokio/*/tokio/stream/trait.Stream.html
//! [`Sink`]: https://docs.rs/futures-sink/*/futures_sink/trait.Sink.html
//! [`Stream`]: https://docs.rs/futures-core/*/futures_core/stream/trait.Stream.html
mod bytes_codec;
pub use self::bytes_codec::BytesCodec;
+2 -2
View File
@@ -1,9 +1,9 @@
use crate::codec::{Decoder, Encoder};
use tokio::net::UdpSocket;
use tokio::{net::UdpSocket, stream::Stream};
use bytes::{BufMut, BytesMut};
use futures_core::{ready, Stream};
use futures_core::ready;
use futures_sink::Sink;
use std::io;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
+1 -2
View File
@@ -1,11 +1,10 @@
#![warn(rust_2018_idioms)]
use tokio::prelude::*;
use tokio::{prelude::*, stream::StreamExt};
use tokio_test::assert_ok;
use tokio_util::codec::{Decoder, Encoder, Framed, FramedParts};
use bytes::{Buf, BufMut, BytesMut};
use futures::StreamExt;
use std::io::{self, Read};
use std::pin::Pin;
use std::task::{Context, Poll};
+1 -2
View File
@@ -1,4 +1,4 @@
use tokio::net::UdpSocket;
use tokio::{net::UdpSocket, stream::StreamExt};
use tokio_util::codec::{Decoder, Encoder};
use tokio_util::udp::UdpFramed;
@@ -6,7 +6,6 @@ use bytes::{BufMut, BytesMut};
use futures::future::try_join;
use futures::future::FutureExt;
use futures::sink::SinkExt;
use futures::stream::StreamExt;
use std::io;
#[tokio::test]
+2 -2
View File
@@ -36,7 +36,7 @@ pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
///
/// [`read_dir`]: read_dir
/// [`DirEntry`]: DirEntry
/// [`Stream`]: futures_core::Stream
/// [`Stream`]: crate::stream::Stream
/// [`Err`]: std::result::Result::Err
#[derive(Debug)]
#[must_use = "streams do nothing unless polled"]
@@ -85,7 +85,7 @@ impl ReadDir {
}
#[cfg(feature = "stream")]
impl futures_core::Stream for ReadDir {
impl crate::stream::Stream for ReadDir {
type Item = io::Result<DirEntry>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+1 -1
View File
@@ -226,8 +226,8 @@ cfg_io_util! {
///
/// ```
/// use tokio::io::AsyncBufReadExt;
/// use tokio::stream::StreamExt;
///
/// use futures::{StreamExt};
/// use std::io::Cursor;
///
/// #[tokio::main]
+1 -1
View File
@@ -91,7 +91,7 @@ where
}
#[cfg(feature = "stream")]
impl<R: AsyncBufRead> futures_core::Stream for Lines<R> {
impl<R: AsyncBufRead> crate::stream::Stream for Lines<R> {
type Item = io::Result<String>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+1 -1
View File
@@ -89,7 +89,7 @@ where
}
#[cfg(feature = "stream")]
impl<R: AsyncBufRead> futures_core::Stream for Split<R> {
impl<R: AsyncBufRead> crate::stream::Stream for Split<R> {
type Item = io::Result<Vec<u8>>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+4
View File
@@ -241,6 +241,10 @@ cfg_signal! {
pub mod signal;
}
cfg_stream! {
pub mod stream;
}
cfg_sync! {
pub mod sync;
}
+1 -1
View File
@@ -28,7 +28,7 @@ impl Incoming<'_> {
}
#[cfg(feature = "stream")]
impl futures_core::Stream for Incoming<'_> {
impl crate::stream::Stream for Incoming<'_> {
type Item = io::Result<TcpStream>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+1 -3
View File
@@ -250,9 +250,7 @@ impl TcpListener {
/// # Examples
///
/// ```no_run
/// use tokio::net::TcpListener;
///
/// use futures::StreamExt;
/// use tokio::{net::TcpListener, stream::StreamExt};
///
/// #[tokio::main]
/// async fn main() {
+1 -1
View File
@@ -27,7 +27,7 @@ impl Incoming<'_> {
}
#[cfg(feature = "stream")]
impl futures_core::Stream for Incoming<'_> {
impl crate::stream::Stream for Incoming<'_> {
type Item = io::Result<UnixStream>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+1 -2
View File
@@ -104,8 +104,7 @@ impl UnixListener {
///
/// ```no_run
/// use tokio::net::UnixListener;
///
/// use futures::StreamExt;
/// use tokio::stream::StreamExt;
///
/// #[tokio::main]
/// async fn main() {
+1 -1
View File
@@ -482,7 +482,7 @@ impl Signal {
}
cfg_stream! {
impl futures_core::Stream for Signal {
impl crate::stream::Stream for Signal {
type Item = ();
fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<()>> {
+3 -3
View File
@@ -209,7 +209,7 @@ impl CtrlBreak {
}
cfg_stream! {
impl futures_core::Stream for CtrlBreak {
impl crate::stream::Stream for CtrlBreak {
type Item = ();
fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<()>> {
@@ -246,9 +246,9 @@ pub fn ctrl_break() -> io::Result<CtrlBreak> {
mod tests {
use super::*;
use crate::runtime::Runtime;
use tokio_test::{assert_ok, assert_pending, assert_ready_ok, task};
use crate::stream::StreamExt;
use futures::stream::StreamExt;
use tokio_test::{assert_ok, assert_pending, assert_ready_ok, task};
#[test]
fn ctrl_c() {
+52
View File
@@ -0,0 +1,52 @@
use crate::stream::Stream;
use core::pin::Pin;
use core::task::{Context, Poll};
/// Stream for the [`iter`] function.
#[derive(Debug)]
#[must_use = "streams do nothing unless polled"]
pub struct Iter<I> {
iter: I,
}
impl<I> Unpin for Iter<I> {}
/// Converts an `Iterator` into a `Stream` which is always ready
/// to yield the next value.
///
/// Iterators in Rust don't express the ability to block, so this adapter
/// simply always calls `iter.next()` and returns that.
///
/// ```
/// # async fn dox() {
/// use tokio::stream::{self, StreamExt};
///
/// let mut stream = stream::iter(vec![17, 19]);
///
/// assert_eq!(stream.next().await, Some(17));
/// assert_eq!(stream.next().await, Some(19));
/// assert_eq!(stream.next().await, None);
/// # }
/// ```
pub fn iter<I>(i: I) -> Iter<I::IntoIter>
where I: IntoIterator,
{
Iter {
iter: i.into_iter(),
}
}
impl<I> Stream for Iter<I>
where I: Iterator,
{
type Item = I::Item;
fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<I::Item>> {
Poll::Ready(self.iter.next())
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
+57
View File
@@ -0,0 +1,57 @@
use crate::stream::Stream;
use core::fmt;
use core::pin::Pin;
use core::task::{Context, Poll};
use pin_project_lite::pin_project;
pin_project! {
/// Stream for the [`map`](super::StreamExt::map) method.
#[must_use = "streams do nothing unless polled"]
pub struct Map<St, F> {
#[pin]
stream: St,
f: F,
}
}
impl<St, F> fmt::Debug for Map<St, F>
where
St: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Map")
.field("stream", &self.stream)
.finish()
}
}
impl<St, T, F> Map<St, F>
where St: Stream,
F: FnMut(St::Item) -> T,
{
pub(super) fn new(stream: St, f: F) -> Map<St, F> {
Map { stream, f }
}
}
impl<St, F, T> Stream for Map<St, F>
where St: Stream,
F: FnMut(St::Item) -> T,
{
type Item = T;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<T>> {
self.as_mut()
.project().stream
.poll_next(cx)
.map(|opt| opt.map(|x| (self.as_mut().project().f)(x)))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.stream.size_hint()
}
}
+93
View File
@@ -0,0 +1,93 @@
//! Stream utilities for Tokio.
//!
//! `Stream`s are an asynchoronous version of standard library's Iterator.
//!
//! This module provides helpers to work with them.
mod iter;
pub use iter::{iter, Iter};
mod map;
use map::Map;
mod next;
use next::Next;
pub use futures_core::Stream;
/// An extension trait for `Stream`s that provides a variety of convenient
/// combinator functions.
pub trait StreamExt: Stream {
/// Creates a future that resolves to the next item in the stream.
///
/// Equivalent to:
///
/// ```ignore
/// async fn next(&mut self) -> Option<Self::Item>;
/// ```
///
/// Note that because `next` doesn't take ownership over the stream,
/// the [`Stream`] type must be [`Unpin`]. If you want to use `next` with a
/// [`!Unpin`](Unpin) stream, you'll first have to pin the stream. This can
/// be done by boxing the stream using [`Box::pin`] or
/// pinning it to the stack using the `pin_mut!` macro from the `pin_utils`
/// crate.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio::stream::{self, StreamExt};
///
/// let mut stream = stream::iter(1..=3);
///
/// assert_eq!(stream.next().await, Some(1));
/// assert_eq!(stream.next().await, Some(2));
/// assert_eq!(stream.next().await, Some(3));
/// assert_eq!(stream.next().await, None);
/// # }
/// ```
fn next(&mut self) -> Next<'_, Self>
where
Self: Unpin,
{
Next::new(self)
}
/// Maps this stream's items to a different type, returning a new stream of
/// the resulting type.
///
/// The provided closure is executed over all elements of this stream as
/// they are made available. It is executed inline with calls to
/// [`poll_next`](Stream::poll_next).
///
/// Note that this function consumes the stream passed into it and returns a
/// wrapped version of it, similar to the existing `map` methods in the
/// standard library.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() {
/// use tokio::stream::{self, StreamExt};
///
/// let stream = stream::iter(1..=3);
/// let mut stream = stream.map(|x| x + 3);
///
/// assert_eq!(stream.next().await, Some(4));
/// assert_eq!(stream.next().await, Some(5));
/// assert_eq!(stream.next().await, Some(6));
/// # }
/// ```
fn map<T, F>(self, f: F) -> Map<Self, F>
where
F: FnMut(Self::Item) -> T,
Self: Sized,
{
Map::new(self, f)
}
}
impl<T: ?Sized> StreamExt for T where T: Stream {}
+31
View File
@@ -0,0 +1,31 @@
use crate::stream::Stream;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
/// Future for the [`next`](super::StreamExt::next) method.
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Next<'a, St: ?Sized> {
stream: &'a mut St,
}
impl<St: ?Sized + Unpin> Unpin for Next<'_, St> {}
impl<'a, St: ?Sized + Stream + Unpin> Next<'a, St> {
pub(super) fn new(stream: &'a mut St) -> Self {
Next { stream }
}
}
impl<St: ?Sized + Stream + Unpin> Future for Next<'_, St> {
type Output = Option<St::Item>;
fn poll(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Self::Output> {
Pin::new(&mut self.stream).poll_next(cx)
}
}
+1 -1
View File
@@ -177,7 +177,7 @@ impl<T> Receiver<T> {
impl<T> Unpin for Receiver<T> {}
cfg_stream! {
impl<T> futures_core::Stream for Receiver<T> {
impl<T> crate::stream::Stream for Receiver<T> {
type Item = T;
fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
+1 -1
View File
@@ -148,7 +148,7 @@ impl<T> UnboundedReceiver<T> {
}
#[cfg(feature = "stream")]
impl<T> futures_core::Stream for UnboundedReceiver<T> {
impl<T> crate::stream::Stream for UnboundedReceiver<T> {
type Item = T;
fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
+1 -1
View File
@@ -268,7 +268,7 @@ impl<T: Clone> Receiver<T> {
}
#[cfg(feature = "stream")]
impl<T: Clone> futures_core::Stream for Receiver<T> {
impl<T: Clone> crate::stream::Stream for Receiver<T> {
type Item = T;
fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<T>> {
+1 -1
View File
@@ -130,7 +130,7 @@ impl Interval {
}
#[cfg(feature = "stream")]
impl futures_core::Stream for Interval {
impl crate::stream::Stream for Interval {
type Item = Instant;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Instant>> {
+2 -2
View File
@@ -1,5 +1,6 @@
//! Slow down a stream by enforcing a delay between items.
use crate::stream::Stream;
use crate::time::{Delay, Duration, Instant};
use std::future::Future;
@@ -7,7 +8,6 @@ use std::marker::Unpin;
use std::pin::Pin;
use std::task::{self, Poll};
use futures_core::Stream;
use pin_project_lite::pin_project;
/// Slow down a stream by enforcing a delay between items.
@@ -17,8 +17,8 @@ use pin_project_lite::pin_project;
///
/// Create a throttled stream.
/// ```rust,norun
/// use futures::stream::StreamExt;
/// use std::time::Duration;
/// use tokio::stream::StreamExt;
/// use tokio::time::throttle;
///
/// # async fn dox() {
+1 -1
View File
@@ -71,7 +71,7 @@ async fn read_inherent() {
#[tokio::test]
async fn read_stream() {
use futures::StreamExt;
use tokio::stream::StreamExt;
let base_dir = tempdir().unwrap();
+1 -1
View File
@@ -20,7 +20,7 @@ async fn lines_inherent() {
#[tokio::test]
async fn lines_stream() {
use futures::StreamExt;
use tokio::stream::StreamExt;
let rd: &[u8] = b"hello\r\nworld\n\n";
let mut st = rd.lines();
+2 -2
View File
@@ -42,7 +42,7 @@ fn send_recv_with_buffer() {
#[tokio::test]
async fn send_recv_stream_with_buffer() {
use futures::StreamExt;
use tokio::stream::StreamExt;
let (mut tx, mut rx) = mpsc::channel::<i32>(16);
@@ -147,7 +147,7 @@ async fn async_send_recv_unbounded() {
#[tokio::test]
async fn send_recv_stream_unbounded() {
use futures::StreamExt;
use tokio::stream::StreamExt;
let (tx, mut rx) = mpsc::unbounded_channel::<i32>();
+1 -1
View File
@@ -195,7 +195,7 @@ fn poll_close() {
#[test]
fn stream_impl() {
use futures::StreamExt;
use tokio::stream::StreamExt;
let (tx, mut rx) = watch::channel("one");
+1 -1
View File
@@ -45,7 +45,7 @@ async fn usage() {
#[tokio::test]
async fn usage_stream() {
use futures::StreamExt;
use tokio::stream::StreamExt;
let start = Instant::now();
let mut interval = time::interval(ms(10));