What BufReader does

A wrapper that adds buffered reads on top of any io::Read source.

As a struct, BufReader bundles related fields into a single named record with a known layout. Each field has a fixed type and offset, so the size of an instance is the sum of its field sizes plus alignment padding. Construction follows ordinary Rust ownership rules: each owned field must itself be constructed or moved before the struct is well-formed, and the compiler will refuse to leave any field uninitialised. The full canonical path is std::io::BufReader; bring it into scope with use std::io::BufReader; or refer to it by its full path.

When to use it

Wrap a File, TcpStream, or any Read with BufReader whenever you read in many small chunks (lines, bytes). Without buffering, every read is a syscall — with buffering, syscalls are amortised over many calls.

Annotated examples

Reading a file line by line efficiently

use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn count_non_blank(path: &str) -> io::Result<usize> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let mut count = 0;
    for line in reader.lines() {
        let line = line?;
        if !line.trim().is_empty() { count += 1; }
    }
    Ok(count)
}

BufReader::lines is the idiomatic line iterator. Each call to lines.next() does in-buffer parsing, only triggering a real syscall when the internal buffer is empty.

For deeper background, see the canonical Rust patterns reference for the broader context behind this section.

Custom buffer size via BufReader::with_capacity

use std::io::{BufReader, Read};
use std::fs::File;

fn slurp(path: &str) -> std::io::Result<Vec<u8>> {
    let f = File::open(path)?;
    // The default 8 KiB buffer is fine for line-oriented reads;
    // for whole-file reads on large files, a 64 KiB buffer is faster.
    let mut r = BufReader::with_capacity(64 * 1024, f);
    let mut buf = Vec::new();
    r.read_to_end(&mut buf)?;
    Ok(buf)
}

For sequential bulk reads, increasing the buffer size to 32 - 64 KiB roughly matches the page-cache prefetch unit on modern Linux and noticeably reduces syscall overhead.

Common pitfalls

Don't mix buffered and unbuffered reads on the same underlying handle — bytes already in the BufReader's buffer will not appear in a direct read of the inner File. If you need to fall back to the underlying reader, call `into_inner()` to consume the BufReader.

For deeper background, see an in-depth Rust idioms cheat sheet for the broader context behind this section.

Performance & threading notes

Stack-allocated by default. Heap allocation only happens if a field is itself heap-allocated (Vec, String, Box). Cloning copies every field; for read access pass by reference. Send / Sync are automatically derived if every field is Send / Sync.