docs: improve tokio::io API documentation (#1815)

Adds method level documentation for `tokio::io`.
This commit is contained in:
Carl Lerche
2019-11-23 08:24:03 -08:00
committed by GitHub
parent 0bc68adb34
commit 3ecaa6d91c
10 changed files with 493 additions and 71 deletions
+9 -8
View File
@@ -22,12 +22,13 @@
#![warn(rust_2018_idioms)]
use futures::{future::try_join, FutureExt};
use std::{env, error::Error};
use tokio::{
io::AsyncReadExt,
net::{TcpListener, TcpStream},
};
use tokio::io;
use tokio::net::{TcpListener, TcpStream};
use futures::future::try_join;
use futures::FutureExt;
use std::env;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
@@ -58,8 +59,8 @@ async fn transfer(mut inbound: TcpStream, proxy_addr: String) -> Result<(), Box<
let (mut ri, mut wi) = inbound.split();
let (mut ro, mut wo) = outbound.split();
let client_to_server = ri.copy(&mut wo);
let server_to_client = ro.copy(&mut wi);
let client_to_server = io::copy(&mut ri, &mut wo);
let server_to_client = io::copy(&mut ro, &mut wi);
try_join(client_to_server, server_to_client).await?;
+14 -4
View File
@@ -5,11 +5,13 @@ use std::ops::DerefMut;
use std::pin::Pin;
use std::task::{Context, Poll};
/// Read bytes asynchronously.
/// Read bytes from a source.
///
/// This trait inherits from `std::io::Read` and indicates that an I/O object is
/// **non-blocking**. All non-blocking I/O objects must return an error when
/// bytes are unavailable instead of blocking the current thread.
/// This trait is analogous to the [`std::io::Read`] trait, but integrates with
/// the asynchronous task system. In particular, the [`poll_read`] method,
/// unlike [`Read::read`], will automatically queue the current task for wakeup
/// and return if data is not yet available, rather than blocking the calling
/// thread.
///
/// Specifically, this means that the `poll_read` function will return one of
/// the following:
@@ -30,6 +32,14 @@ use std::task::{Context, Poll};
///
/// This trait importantly means that the `read` method only works in the
/// context of a future's task. The object may panic if used outside of a task.
///
/// Utilities for working with `AsyncRead` values are provided by
/// [`AsyncReadExt`].
///
/// [`poll_read`]: AsyncRead::poll_read
/// [`std::io::Read`]: std::io::Read
/// [`Read::read`]: std::io::Read::read
/// [`AsyncReadExt`]: crate::io::AsyncReadExt
pub trait AsyncRead {
/// Prepares an uninitialized buffer to be safe to pass to `read`. Returns
/// `true` if the supplied buffer was zeroed out.
+17 -10
View File
@@ -1,6 +1,6 @@
#![cfg_attr(loom, allow(dead_code, unreachable_pub))]
//! Asynchronous I/O.
//! Traits, helpers, and type definitions for asynchronous I/O functionality.
//!
//! This module is the asynchronous version of `std::io`. Primarily, it
//! defines two traits, [`AsyncRead`] and [`AsyncWrite`], which are asynchronous
@@ -15,7 +15,19 @@
//! type will _yield_ to the Tokio scheduler when IO is not ready, rather than
//! blocking. This allows other tasks to run while waiting on IO.
//!
//! In asynchronous programs, Tokio's [`AsyncRead`] and [`AsyncWrite`] traits
//! Another difference is that [`AsyncRead`] and [`AsyncWrite`] only contain
//! core methods needed to provide asynchronous reading and writing
//! functionality. Instead, utility methods are defined in the [`AsyncReadExt`]
//! and [`AsyncWriteExt`] extension traits. These traits are automatically
//! implemented for all values that implement [`AsyncRead`] and [`AsyncWrite`]
//! respectively.
//!
//! End users will rarely interact directly with [`AsyncRead`] and
//! [`AsyncWrite`]. Instead, they will use the async functions defined in the
//! extension traits. Library authors are expected to implement [`AsyncRead`]
//! and [`AsyncWrite`] in order to provide types that behave like byte streams.
//!
//! Even with these differences, Tokio's [`AsyncRead`] and [`AsyncWrite`] traits
//! can be used in almost exactly the same manner as the standard library's
//! `Read` and `Write`. Most types in the standard library that implement `Read`
//! and `Write` have asynchronous equivalents in `tokio` that implement
@@ -26,8 +38,7 @@
//! can do the same with [`tokio::fs::File`][`File`]:
//!
//! ```no_run
//! use tokio::io;
//! use tokio::prelude::*;
//! use tokio::io::{self, AsyncReadExt};
//! use tokio::fs::File;
//!
//! #[tokio::main]
@@ -64,10 +75,8 @@
//! extra methods to any async reader:
//!
//! ```no_run
//! use tokio::io;
//! use tokio::io::BufReader;
//! use tokio::io::{self, BufReader, AsyncBufReadExt};
//! use tokio::fs::File;
//! use tokio::prelude::*;
//!
//! #[tokio::main]
//! async fn main() -> io::Result<()> {
@@ -87,10 +96,8 @@
//! to [`write`](crate::io::AsyncWriteExt::write):
//!
//! ```no_run
//! use tokio::io;
//! use tokio::io::BufWriter;
//! use tokio::io::{self, BufWriter, AsyncWriteExt};
//! use tokio::fs::File;
//! use tokio::prelude::*;
//!
//! #[tokio::main]
//! async fn main() -> io::Result<()> {
+277 -36
View File
@@ -1,20 +1,70 @@
use crate::io::util::chain::{chain, Chain};
use crate::io::util::copy::{copy, Copy};
use crate::io::util::read::{read, Read};
use crate::io::util::read_exact::{read_exact, ReadExact};
use crate::io::util::read_to_end::{read_to_end, ReadToEnd};
use crate::io::util::read_to_string::{read_to_string, ReadToString};
use crate::io::util::take::{take, Take};
use crate::io::{AsyncRead, AsyncWrite};
use crate::io::AsyncRead;
cfg_io_util! {
/// An extension trait which adds utility methods to `AsyncRead` types.
/// Read bytes from a source.
///
/// Implemented as an extention trait, adding utility methods to all
/// [`AsyncRead`] types. Callers will tend to import this trait instead of
/// [`AsyncRead`].
///
/// As a convenience, this trait may be imported using the [`prelude`]:
///
/// ```no_run
/// use tokio::fs::File;
/// use tokio::prelude::*;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut f = File::open("foo.txt").await?;
/// let mut buffer = [0; 10];
///
/// // The `read` method is defined by this trait.
/// let n = f.read(&mut buffer[..]).await?;
///
/// Ok(())
/// }
/// ```
///
/// See [module][crate::io] documentation for more details.
///
/// [`AsyncRead`]: AsyncRead
/// [`prelude`]: crate::prelude
pub trait AsyncReadExt: AsyncRead {
/// Creates an adaptor which will chain this stream with another.
/// Create a new `AsyncRead` instance that chains this stream with
/// `next`.
///
/// The returned `AsyncRead` instance will first read all bytes from this object
/// until EOF is encountered. Afterwards the output is equivalent to the
/// output of `next`.
///
/// # Examples
///
/// [`File`][crate::fs::File]s implement `AsyncRead`:
///
/// ```no_run
/// use tokio::fs::File;
/// use tokio::io::{self, AsyncReadExt};
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let f1 = File::open("foo.txt").await?;
/// let f2 = File::open("bar.txt").await?;
///
/// let mut handle = f1.chain(f2);
/// let mut buffer = String::new();
///
/// // read the value into a String. We could use any AsyncRead
/// // method here, this is just one example.
/// handle.read_to_string(&mut buffer).await?;
/// Ok(())
/// }
/// ```
fn chain<R>(self, next: R) -> Chain<Self, R>
where
Self: Sized,
@@ -23,56 +73,222 @@ cfg_io_util! {
chain(self, next)
}
/// Copy all data from `self` into the provided `AsyncWrite`.
/// Pull some bytes from this source into the specified buffer,
/// returning how many bytes were read.
///
/// The returned future will copy all the bytes read from `reader` into the
/// `writer` specified. This future will only complete once the `reader`
/// has hit EOF and all bytes have been written to and flushed from the
/// `writer` provided.
/// Equivalent to:
///
/// On success the number of bytes is returned and the `reader` and `writer`
/// are consumed. On error the error is returned and the I/O objects are
/// consumed as well.
fn copy<'a, W>(&'a mut self, dst: &'a mut W) -> Copy<'a, Self, W>
where
Self: Unpin,
W: AsyncWrite + Unpin + ?Sized,
{
copy(self, dst)
}
/// Read data into the provided buffer.
/// ```ignore
/// async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>;
/// ```
///
/// The returned future will resolve to the number of bytes read once the
/// read operation is completed.
fn read<'a>(&'a mut self, dst: &'a mut [u8]) -> Read<'a, Self>
/// This function does not provide any guarantees about whether it
/// completes immediately or asynchronously
///
/// If the return value of this method is `Ok(n)`, then it must be
/// guaranteed that `0 <= n <= buf.len()`. A nonzero `n` value indicates
/// that the buffer `buf` has been filled in with `n` bytes of data from
/// this source. If `n` is `0`, then it can indicate one of two
/// scenarios:
///
/// 1. This reader has reached its "end of file" and will likely no longer
/// be able to produce bytes. Note that this does not mean that the
/// reader will *always* no longer be able to produce bytes.
/// 2. The buffer specified was 0 bytes in length.
///
/// No guarantees are provided about the contents of `buf` when this
/// function is called, implementations cannot rely on any property of the
/// contents of `buf` being true. It is recommended that *implementations*
/// only write data to `buf` instead of reading its contents.
///
/// Correspondingly, however, *callers* of this method may not assume
/// any guarantees about how the implementation uses `buf`. It is
/// possible that the code that's supposed to write to the buffer might
/// also read from it. It is your responsibility to make sure that `buf`
/// is initialized before calling `read`.
///
/// # Errors
///
/// If this function encounters any form of I/O or other error, an error
/// variant will be returned. If an error is returned then it must be
/// guaranteed that no bytes were read.
///
/// # Examples
///
/// [`File`][crate::fs::File]s implement `Read`:
///
/// ```no_run
/// use tokio::fs::File;
/// use tokio::io::{self, AsyncReadExt};
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut f = File::open("foo.txt").await?;
/// let mut buffer = [0; 10];
///
/// // read up to 10 bytes
/// let n = f.read(&mut buffer[..]).await?;
///
/// println!("The bytes: {:?}", &buffer[..n]);
/// Ok(())
/// }
/// ```
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Read<'a, Self>
where
Self: Unpin,
{
read(self, dst)
read(self, buf)
}
/// Read exactly the amount of data needed to fill the provided buffer.
fn read_exact<'a>(&'a mut self, dst: &'a mut [u8]) -> ReadExact<'a, Self>
/// Read the exact number of bytes required to fill `buf`.
///
/// Equivalent to:
///
/// ```ignore
/// async fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<usize>;
/// ```
///
/// This function reads as many bytes as necessary to completely fill
/// the specified buffer `buf`.
///
/// No guarantees are provided about the contents of `buf` when this
/// function is called, implementations cannot rely on any property of
/// the contents of `buf` being true. It is recommended that
/// implementations only write data to `buf` instead of reading its
/// contents.
///
/// # Errors
///
/// If the operation encounters an "end of file" before completely
/// filling the buffer, it returns an error of the kind
/// [`ErrorKind::UnexpectedEof`]. The contents of `buf` are unspecified
/// in this case.
///
/// If any other read error is encountered then the operation
/// immediately returns. The contents of `buf` are unspecified in this
/// case.
///
/// If this operation returns an error, it is unspecified how many bytes
/// it has read, but it will never read more than would be necessary to
/// completely fill the buffer.
///
/// # Examples
///
/// [`File`][crate::fs::File]s implement `Read`:
///
/// ```no_run
/// use tokio::fs::File;
/// use tokio::io::{self, AsyncReadExt};
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut f = File::open("foo.txt").await?;
/// let mut buffer = [0; 10];
///
/// // read exactly 10 bytes
/// f.read_exact(&mut buffer).await?;
/// Ok(())
/// }
/// ```
///
/// [`ErrorKind::UnexpectedEof`]: std::io::ErrorKind::UnexpectedEof
fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadExact<'a, Self>
where
Self: Unpin,
{
read_exact(self, dst)
read_exact(self, buf)
}
/// Read all bytes until EOF in this source, placing them into `dst`.
/// Read all bytes until EOF in this source, placing them into `buf`.
///
/// On success the total number of bytes read is returned.
fn read_to_end<'a>(&'a mut self, dst: &'a mut Vec<u8>) -> ReadToEnd<'a, Self>
/// Equivalent to:
///
/// ```ignore
/// async fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize>;
/// ```
///
/// All bytes read from this source will be appended to the specified
/// buffer `buf`. This function will continuously call [`read()`] to
/// append more data to `buf` until [`read()`][read] returns `Ok(0)`.
///
/// If successful, the total number of bytes read is returned.
///
/// # Errors
///
/// If a read error is encountered then the `read_to_end` operation
/// immediately completes. Any bytes which have already been read will
/// be appended to `buf`.
///
/// # Examples
///
/// [`File`][crate::fs::File]s implement `Read`:
///
/// ```no_run
/// use tokio::io::{self, AsyncReadExt};
/// use tokio::fs::File;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut f = File::open("foo.txt").await?;
/// let mut buffer = Vec::new();
///
/// // read the whole file
/// f.read_to_end(&mut buffer).await?;
/// Ok(())
/// }
/// ```
///
/// (See also the [`tokio::fs::read`] convenience function for reading from a
/// file.)
///
/// [`tokio::fs::read`]: crate::fs::read::read
fn read_to_end<'a>(&'a mut self, buf: &'a mut Vec<u8>) -> ReadToEnd<'a, Self>
where
Self: Unpin,
{
read_to_end(self, dst)
read_to_end(self, buf)
}
/// Read all bytes until EOF in this source, placing them into `dst`.
/// Read all bytes until EOF in this source, appending them to `buf`.
///
/// On success the total number of bytes read is returned.
/// Equivalent to:
///
/// ```ignore
/// async fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize>;
/// ```
///
/// If successful, the number of bytes which were read and appended to
/// `buf` is returned.
///
/// # Errors
///
/// If the data in this stream is *not* valid UTF-8 then an error is
/// returned and `buf` is unchanged.
///
/// See [`read_to_end`][AsyncReadExt::read_to_end] for other error semantics.
///
/// # Examples
///
/// [`File`][crate::fs::File]s implement `Read`:
///
/// ```no_run
/// use tokio::io::{self, AsyncReadExt};
/// use tokio::fs::File;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut f = File::open("foo.txt").await?;
/// let mut buffer = String::new();
///
/// f.read_to_string(&mut buffer).await?;
/// Ok(())
/// }
/// ```
///
/// (See also the [`crate::fs::read_to_string`] convenience function for
/// reading from a file.)
///
/// [`crate::fs::read_to_string`]: crate::fs::read_to_string::read_to_string
fn read_to_string<'a>(&'a mut self, dst: &'a mut String) -> ReadToString<'a, Self>
where
Self: Unpin,
@@ -80,8 +296,33 @@ cfg_io_util! {
read_to_string(self, dst)
}
/// Creates an AsyncRead adapter which will read at most `limit` bytes
/// from the underlying reader.
/// Creates an adaptor which reads at most `limit` bytes from it.
///
/// This function returns a new instance of `AsyncRead` which will read
/// at most `limit` bytes, after which it will always return EOF
/// (`Ok(0)`). Any read errors will not count towards the number of
/// bytes read and future calls to [`read()`][read] may succeed.
///
/// # Examples
///
/// [`File`][crate::fs::File]s implement `Read`:
///
/// ```no_run
/// use tokio::io::{self, AsyncReadExt};
/// use tokio::fs::File;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let f = File::open("foo.txt").await?;
/// let mut buffer = [0; 5];
///
/// // read at most five bytes
/// let mut handle = f.take(5);
///
/// handle.read(&mut buffer).await?;
/// Ok(())
/// }
/// ```
fn take(self, limit: u64) -> Take<Self>
where
Self: Sized,
+169 -5
View File
@@ -5,9 +5,83 @@ use crate::io::util::write_all::{write_all, WriteAll};
use crate::io::AsyncWrite;
cfg_io_util! {
/// An extension trait which adds utility methods to `AsyncWrite` types.
/// Write bytes to a sink.
///
/// Implemented as an extention trait, adding utility methods to all
/// [`AsyncWrite`] types. Callers will tend to import this trait instead of
/// [`AsyncWrite`].
///
/// As a convenience, this trait may be imported using the [`prelude`]:
///
/// ```no_run
/// use tokio::prelude::*;
/// use tokio::fs::File;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let data = b"some bytes";
///
/// let mut pos = 0;
/// let mut buffer = File::create("foo.txt").await?;
///
/// while pos < data.len() {
/// let bytes_written = buffer.write(&data[pos..]).await?;
/// pos += bytes_written;
/// }
///
/// Ok(())
/// }
/// ```
///
/// See [module][crate::io] documentation for more details.
///
/// [`AsyncWrite`]: AsyncWrite
/// [`prelude`]: crate::prelude
pub trait AsyncWriteExt: AsyncWrite {
/// Write a buffer into this writter, returning how many bytes were written.
/// Write a buffer into this writer, returning how many bytes were
/// written.
///
/// Equivalent to:
///
/// ```ignore
/// async fn write(&mut self, buf: &[u8]) -> io::Result<usize>;
/// ```
///
/// This function will attempt to write the entire contents of `buf`, but
/// the entire write may not succeed, or the write may also generate an
/// error. A call to `write` represents *at most one* attempt to write to
/// any wrapped object.
///
/// If the return value is `Ok(n)` then it must be guaranteed that `n <=
/// buf.len()`. A return value of `0` typically means that the
/// underlying object is no longer able to accept bytes and will likely
/// not be able to in the future as well, or that the buffer provided is
/// empty.
///
/// # Errors
///
/// Each call to `write` may generate an I/O error indicating that the
/// operation could not be completed. If an error is returned then no bytes
/// in the buffer were written to this writer.
///
/// It is **not** considered an error if the entire buffer could not be
/// written to this writer.
///
/// # Examples
///
/// ```no_run
/// use tokio::io::{self, AsyncWriteExt};
/// use tokio::fs::File;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut buffer = File::create("foo.txt").await?;
///
/// // Writes some prefix of the byte string, not necessarily all of it.
/// buffer.write(b"some bytes").await?;
/// Ok(())
/// }
/// ```
fn write<'a>(&'a mut self, src: &'a [u8]) -> Write<'a, Self>
where
Self: Unpin,
@@ -15,7 +89,39 @@ cfg_io_util! {
write(self, src)
}
/// Attempt to write an entire buffer into this writter.
/// Attempts to write an entire buffer into this writer.
///
/// Equivalent to:
///
/// ```ignore
/// async fn write_all(&mut self, buf: &[u8]) -> io::Result<()>;
/// ```
///
/// This method will continuously call [`write`] until there is no more data
/// to be written. This method will not return until the entire buffer
/// has been successfully written or such an error occurs. The first
/// error generated from this method will be returned.
///
/// # Errors
///
/// This function will return the first error that [`write`] returns.
///
/// # Examples
///
/// ```no_run
/// use tokio::io::{self, AsyncWriteExt};
/// use tokio::fs::File;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let mut buffer = File::create("foo.txt").await?;
///
/// buffer.write_all(b"some bytes").await?;
/// Ok(())
/// }
/// ```
///
/// [`write`]: AsyncWriteExt::write
fn write_all<'a>(&'a mut self, src: &'a [u8]) -> WriteAll<'a, Self>
where
Self: Unpin,
@@ -23,7 +129,36 @@ cfg_io_util! {
write_all(self, src)
}
/// Flush the contents of this writer.
/// Flush this output stream, ensuring that all intermediately buffered
/// contents reach their destination.
///
/// Equivalent to:
///
/// ```ignore
/// async fn flush(&mut self) -> io::Result<()>;
/// ```
///
/// # Errors
///
/// It is considered an error if not all bytes could be written due to
/// I/O errors or EOF being reached.
///
/// # Examples
///
/// ```no_run
/// use tokio::io::{self, BufWriter, AsyncWriteExt};
/// use tokio::fs::File;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let f = File::create("foo.txt").await?;
/// let mut buffer = BufWriter::new(f);
///
/// buffer.write_all(b"some bytes").await?;
/// buffer.flush().await?;
/// Ok(())
/// }
/// ```
fn flush(&mut self) -> Flush<'_, Self>
where
Self: Unpin,
@@ -31,7 +166,36 @@ cfg_io_util! {
flush(self)
}
/// Shutdown this writer.
/// Shuts down the output stream, ensuring that the value can be dropped
/// cleanly.
///
/// Equivalent to:
///
/// ```ignore
/// async fn shutdown(&mut self) -> io::Result<()>;
/// ```
///
/// Similar to [`flush`], all intermediately buffered is written to the
/// underlying stream. Once the operation completes, the caller should
/// no longer attempt to write to the stream. For example, the
/// `TcpStream` implementation will issue a `shutdown(Write)` sys call.
///
/// # Examples
///
/// ```no_run
/// use tokio::io::{self, BufWriter, AsyncWriteExt};
/// use tokio::fs::File;
///
/// #[tokio::main]
/// async fn main() -> io::Result<()> {
/// let f = File::create("foo.txt").await?;
/// let mut buffer = BufWriter::new(f);
///
/// buffer.write_all(b"some bytes").await?;
/// buffer.shutdown().await?;
/// Ok(())
/// }
/// ```
fn shutdown(&mut self) -> Shutdown<'_, Self>
where
Self: Unpin,
+1 -1
View File
@@ -13,7 +13,7 @@
//!
//! The prelude may grow over time as additional items see ubiquitous use.
pub use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite};
pub use crate::io::{self, AsyncBufRead, AsyncRead, AsyncWrite};
cfg_io_util! {
#[doc(no_inline)]
+1 -1
View File
@@ -41,7 +41,7 @@ async fn echo_server() {
let (mut a, _) = assert_ok!(srv.accept().await);
let (mut b, _) = assert_ok!(srv.accept().await);
let n = assert_ok!(a.copy(&mut b).await);
let n = assert_ok!(io::copy(&mut a, &mut b).await);
let (expected, t2) = t.join().unwrap();
let actual = t2.join().unwrap();
+2 -3
View File
@@ -1,10 +1,9 @@
#![warn(rust_2018_idioms)]
#![cfg(feature = "full")]
use tokio::io::{AsyncRead, AsyncReadExt};
use tokio::io::{self, AsyncRead};
use tokio_test::assert_ok;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -31,7 +30,7 @@ async fn copy() {
let mut rd = Rd(true);
let mut wr = Vec::new();
let n = assert_ok!(rd.copy(&mut wr).await);
let n = assert_ok!(io::copy(&mut rd, &mut wr).await);
assert_eq!(n, 11);
assert_eq!(wr, b"hello world");
}
+1 -1
View File
@@ -35,7 +35,7 @@ async fn echo_server() {
let (mut stream, _) = assert_ok!(srv.accept().await);
let (mut rd, mut wr) = stream.split();
let n = assert_ok!(rd.copy(&mut wr).await);
let n = assert_ok!(io::copy(&mut rd, &mut wr).await);
assert_eq!(n, (ITER * msg.len()) as u64);
assert_ok!(rx.await);
+2 -2
View File
@@ -1,7 +1,7 @@
#![warn(rust_2018_idioms)]
#![cfg(feature = "full")]
use tokio::io::AsyncWriteExt;
use tokio::io::{self, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::prelude::*;
use tokio_test::assert_ok;
@@ -24,6 +24,6 @@ async fn shutdown() {
let (mut stream, _) = assert_ok!(srv.accept().await);
let (mut rd, mut wr) = stream.split();
let n = assert_ok!(rd.copy(&mut wr).await);
let n = assert_ok!(io::copy(&mut rd, &mut wr).await);
assert_eq!(n, 0);
}