Read and Write Traits
std::io::Read and std::io::Write are the two foundational byte-stream traits in the Rust standard library. By writing functions generic over R: Read and W: Write, your parsing and serialization code works identically across files (File), network sockets (TcpStream), standard streams (Stdin, Stdout), compressed streams, and in-memory byte buffers (&[u8], Vec<u8>, Cursor<T>).
Essential Methods of std::io::Read
Only a single method (fn read(&mut self, buf: &mut [u8]) -> Result<usize>) is required to implement Read; all other methods are provided automatically as default methods:
Read Method |
Signature | Behavior |
|---|---|---|
.read(buf) (required) |
fn read(&mut self, buf: &mut [u8]) -> Result<usize> |
Pulls up to buf.len() bytes; may return fewer bytes (short read); Ok(0) indicates EOF (when buf is non-empty) |
.read_exact(buf) |
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> |
Loops calling .read() (retrying Interrupted) until buf is completely filled, or returns Err(UnexpectedEof) |
.read_to_end(&mut vec) |
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> |
Reads all remaining bytes until EOF and appends them to vec |
.read_to_string(&mut s) |
fn read_to_string(&mut self, buf: &mut String) -> Result<usize> |
Reads all bytes until EOF, validates UTF-8, and appends to s |
.take(limit) |
fn take(self, limit: u64) -> Take<Self> |
Adapter that caps reading at limit bytes (essential for preventing OOM on untrusted network streams!) |
.chain(next) |
fn chain<R: Read>(self, next: R) -> Chain<Self, R> |
Concatenates two readers sequentially |
.by_ref() |
fn by_ref(&mut self) -> &mut Self |
Borrows the reader mutably so .take(n) can be used without consuming self! |
Essential Methods of std::io::Write
Implementing Write requires two methods: fn write(&mut self, buf: &[u8]) -> Result<usize> and fn flush(&mut self) -> Result<()>:
Write Method |
Signature | Behavior |
|---|---|---|
.write(buf) (required) |
fn write(&mut self, buf: &[u8]) -> Result<usize> |
Writes up to buf.len() bytes and returns how many bytes were accepted (short write possible!) |
.flush() (required) |
fn flush(&mut self) -> Result<()> |
Ensures all intermediately buffered bytes reach the underlying OS destination |
.write_all(buf) |
fn write_all(&mut self, buf: &[u8]) -> Result<()> |
Loops calling .write() until every byte in buf has been written |
.write_fmt(args) |
fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> |
Powers write!(w, ...) and writeln!(w, ...) on any Write type! |
Why You Should Almost Always Call read_exact / write_all Instead of Raw read / write
A single call to .read(&mut buf) or .write(&buf) on a network socket or pipe is allowed by the OS kernel to transfer only part of the buffer (for example, returning Ok(4) when you asked to read or write 16 bytes). Using .read_exact(&mut buf) and .write_all(&buf) handles partial transfers and ErrorKind::Interrupted retries automatically:
use std::io::{self, Read, Write};
fn encode_length_prefixed(mut writer: impl Write, payload: &[u8]) -> io::Result<()> {
let len = u32::try_from(payload.len()).expect("payload too large");
writer.write_all(&len.to_be_bytes())?;
writer.write_all(payload)?;
writer.flush()
}
fn decode_length_prefixed(mut reader: impl Read, max_len: u64) -> io::Result<Vec<u8>> {
let mut len_bytes = [0_u8; 4];
reader.read_exact(&mut len_bytes)?;
let len = u32::from_be_bytes(len_bytes);
let mut payload = Vec::new();
// Use `.by_ref().take(max_len)` to cap untrusted allocation size safely:
reader
.by_ref()
.take(u64::from(len).min(max_len))
.read_to_end(&mut payload)?;
Ok(payload)
}
fn main() -> io::Result<()> {
// `Vec<u8>` implements `Write`, and `&[u8]` implements `Read`!
let mut wire_bytes: Vec<u8> = Vec::new();
encode_length_prefixed(&mut wire_bytes, b"frame-payload")?;
let decoded = decode_length_prefixed(&wire_bytes[..], 1024)?;
println!("wire bytes ({} B): {:02x?}", wire_bytes.len(), wire_bytes);
println!("decoded payload: {:?}", String::from_utf8_lossy(&decoded));
Ok(())
}
wire bytes (17 B): [00, 00, 00, 0d, 66, 72, 61, 6d, 65, 2d, 70, 61, 79, 6c, 6f, 61, 64]
decoded payload: "frame-payload"