Traits, helpers, and type definitions for asynchronous I/O functionality in Tokio.
Overview
This module is the asynchronous version of std::io. It defines two core traits:
AsyncRead: Asynchronous reading of bytes from a source
AsyncWrite: Asynchronous writing of bytes to a destination
Unlike standard library traits, Tokio’s I/O traits yield to the scheduler when I/O is not ready, allowing other tasks to run.
Core Traits
AsyncRead
Reads bytes asynchronously from a source.Required Method:fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>>
Example Implementation:use tokio::io::{AsyncRead, ReadBuf};
use std::pin::Pin;
use std::task::{Context, Poll};
struct MyReader {
// ... fields
}
impl AsyncRead for MyReader {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
// Implementation
Poll::Ready(Ok(()))
}
}
AsyncWrite
Writes bytes asynchronously to a destination.Required Methods:fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>>
fn poll_flush(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>>
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>>
Extension Traits
Most users interact with I/O through extension traits that provide convenient async methods.
AsyncReadExt
Provides utility methods for types implementing AsyncRead.
read
async fn(&mut self, buf: &mut [u8]) -> io::Result<usize>
Reads some bytes from the source into the buffer.Example:use tokio::fs::File;
use tokio::io::AsyncReadExt;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut file = File::open("foo.txt").await?;
let mut buffer = [0; 10];
let n = file.read(&mut buffer).await?;
println!("Read {} bytes: {:?}", n, &buffer[..n]);
Ok(())
}
read_exact
async fn(&mut self, buf: &mut [u8]) -> io::Result<()>
Reads exactly the number of bytes required to fill the buffer.Example:use tokio::io::AsyncReadExt;
let mut buffer = [0; 10];
reader.read_exact(&mut buffer).await?;
// buffer is now completely filled
read_to_end
async fn(&mut self, buf: &mut Vec<u8>) -> io::Result<usize>
Reads all bytes until EOF, appending them to the vector.Example:use tokio::fs::File;
use tokio::io::AsyncReadExt;
let mut file = File::open("foo.txt").await?;
let mut contents = Vec::new();
file.read_to_end(&mut contents).await?;
read_to_string
async fn(&mut self, buf: &mut String) -> io::Result<usize>
Reads all bytes until EOF, appending them to the string.Example:use tokio::fs::File;
use tokio::io::AsyncReadExt;
let mut file = File::open("foo.txt").await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
println!("File contents: {}", contents);
AsyncWriteExt
Provides utility methods for types implementing AsyncWrite.
write
async fn(&mut self, buf: &[u8]) -> io::Result<usize>
Writes a buffer into this writer, returning how many bytes were written.Example:use tokio::fs::File;
use tokio::io::AsyncWriteExt;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut file = File::create("foo.txt").await?;
let n = file.write(b"hello world").await?;
println!("Wrote {} bytes", n);
Ok(())
}
write_all
async fn(&mut self, buf: &[u8]) -> io::Result<()>
Writes the entire buffer to this writer.Example:use tokio::io::AsyncWriteExt;
writer.write_all(b"hello world").await?;
// All bytes have been written
flush
async fn(&mut self) -> io::Result<()>
Flushes this writer, ensuring all buffered data reaches the destination.Example:use tokio::io::AsyncWriteExt;
writer.write_all(b"data").await?;
writer.flush().await?;
shutdown
async fn(&mut self) -> io::Result<()>
Shuts down the writer, flushing any buffered data.Example:use tokio::io::AsyncWriteExt;
writer.write_all(b"goodbye").await?;
writer.shutdown().await?;
Buffered I/O
BufReader
BufReader::new
fn<R: AsyncRead>(inner: R) -> BufReader<R>
Creates a new buffered reader.Example:use tokio::fs::File;
use tokio::io::{BufReader, AsyncBufReadExt};
#[tokio::main]
async fn main() -> io::Result<()> {
let file = File::open("foo.txt").await?;
let mut reader = BufReader::new(file);
let mut line = String::new();
reader.read_line(&mut line).await?;
println!("First line: {}", line);
Ok(())
}
BufWriter
BufWriter::new
fn<W: AsyncWrite>(inner: W) -> BufWriter<W>
Creates a new buffered writer.Example:use tokio::fs::File;
use tokio::io::{BufWriter, AsyncWriteExt};
#[tokio::main]
async fn main() -> io::Result<()> {
let file = File::create("foo.txt").await?;
let mut writer = BufWriter::new(file);
writer.write_all(b"hello").await?;
writer.flush().await?; // Don't forget to flush!
Ok(())
}
BufWriter buffers writes. You must call flush() or shutdown() to ensure data is written before the writer is dropped.
AsyncBufReadExt
read_line
async fn(&mut self, buf: &mut String) -> io::Result<usize>
Reads a line into the string, including the newline character.Example:use tokio::io::{BufReader, AsyncBufReadExt};
use tokio::fs::File;
let file = File::open("foo.txt").await?;
let mut reader = BufReader::new(file);
let mut line = String::new();
while reader.read_line(&mut line).await? > 0 {
println!("Line: {}", line);
line.clear();
}
Returns a stream of lines from the reader.Example:use tokio::io::{BufReader, AsyncBufReadExt};
use tokio::fs::File;
let file = File::open("foo.txt").await?;
let reader = BufReader::new(file);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await? {
println!("Line: {}", line);
}
Utility Functions
copy
copy
async fn<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
Copies all bytes from reader to writer.Example:use tokio::fs::File;
use tokio::io;
#[tokio::main]
async fn main() -> io::Result<()> {
let mut source = File::open("source.txt").await?;
let mut dest = File::create("dest.txt").await?;
let bytes_copied = io::copy(&mut source, &mut dest).await?;
println!("Copied {} bytes", bytes_copied);
Ok(())
}
split
split
fn<T: AsyncRead + AsyncWrite>(stream: T) -> (ReadHalf<T>, WriteHalf<T>)
Splits a stream into a read half and write half.Example:use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
let stream = TcpStream::connect("127.0.0.1:8080").await?;
let (mut reader, mut writer) = io::split(stream);
tokio::spawn(async move {
writer.write_all(b"hello").await.unwrap();
});
let mut buf = [0; 1024];
let n = reader.read(&mut buf).await?;
duplex
duplex
fn(max_buf_size: usize) -> (DuplexStream, DuplexStream)
Creates an in-memory duplex stream. Data written to one half can be read from the other.Example:use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> io::Result<()> {
let (mut client, mut server) = io::duplex(64);
client.write_all(b"ping").await?;
let mut buf = [0; 4];
server.read_exact(&mut buf).await?;
assert_eq!(&buf, b"ping");
Ok(())
}
Standard I/O
Returns a handle to standard input.Example:use tokio::io::{self, AsyncBufReadExt, BufReader};
#[tokio::main]
async fn main() -> io::Result<()> {
let mut stdin = BufReader::new(io::stdin());
let mut line = String::new();
println!("Enter a line:");
stdin.read_line(&mut line).await?;
println!("You entered: {}", line);
Ok(())
}
Returns a handle to standard output.Example:use tokio::io::{self, AsyncWriteExt};
#[tokio::main]
async fn main() -> io::Result<()> {
let mut stdout = io::stdout();
stdout.write_all(b"Hello, world!\n").await?;
Ok(())
}
Returns a handle to standard error.
Practical Examples
Reading a File Line by Line
use tokio::fs::File;
use tokio::io::{self, AsyncBufReadExt, BufReader};
#[tokio::main]
async fn main() -> io::Result<()> {
let file = File::open("input.txt").await?;
let reader = BufReader::new(file);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await? {
println!("Line: {}", line);
}
Ok(())
}
Echo Server
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buf = [0; 1024];
loop {
match socket.read(&mut buf).await {
Ok(0) => return, // Connection closed
Ok(n) => {
if socket.write_all(&buf[..n]).await.is_err() {
return;
}
}
Err(_) => return,
}
}
});
}
}
Copying with Progress
use tokio::fs::File;
use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
async fn copy_with_progress(
src: &str,
dst: &str,
) -> io::Result<u64> {
let mut source = File::open(src).await?;
let mut dest = File::create(dst).await?;
let mut buf = vec![0; 8192];
let mut total = 0u64;
loop {
let n = source.read(&mut buf).await?;
if n == 0 {
break;
}
dest.write_all(&buf[..n]).await?;
total += n as u64;
println!("Copied {} bytes", total);
}
dest.flush().await?;
Ok(total)
}
Best Practices
Use buffered I/O
Wrap readers and writers with BufReader and BufWriter for better performance.
Always flush buffers
Call flush() or shutdown() on buffered writers before dropping them.
Handle EOF properly
Check for 0-byte reads which indicate end of stream.
Use extension traits
Import AsyncReadExt and AsyncWriteExt for convenient methods.
For network protocols, consider using BufReader to efficiently read data in patterns like line-by-line or length-prefixed messages.
Don’t perform blocking I/O operations (like std::fs or blocking network calls) from async contexts. Use tokio::fs and tokio::net instead.