Standard Input and Output

Buffered Input

Buffered input wraps an unbuffered reader (std::fs::File or std::net::TcpStream) in an in-memory byte array (typically 8 KiB) so that small, frequent read operations are served directly from user-space RAM instead of making an expensive operating-system kernel call (read syscall) per byte or per line.

Unbuffered vs. Buffered Read Cost

Every OS system call (read(fd, ...) on Unix, ReadFile on Windows) switches the CPU from user mode (Ring 3) to kernel mode (Ring 0) and back, costing several hundred nanoseconds. Reading a 1 MiB file 1 byte at a time via an unbuffered File executes 1,048,576 system calls, whereas reading it through an 8 KiB BufReader<File> executes only 128 system calls—often 100x to 500x faster!

Unbuffered `File::read(&mut [u8; 4])` (1 OS Syscall per Small Read):
  [App] ---syscall---> [OS Kernel] (copies 4 bytes)
  [App] ---syscall---> [OS Kernel] (copies 4 bytes)
  [App] ---syscall---> [OS Kernel] (copies 4 bytes)

Buffered `BufReader<File>` (1 OS Syscall Fills 8,192-Byte RAM Buffer):
  [App] <--memcpy 4B-- [BufReader: 8 KiB RAM Buffer] <---(1 syscall per 8 KiB)--- [OS Kernel]
  [App] <--memcpy 4B-- [BufReader: 8 KiB RAM Buffer] (Served in nanoseconds from RAM!)

When to Use Buffered Input vs. Unbuffered Input

Access Pattern Use BufReader<R>? Why
Line-by-line or token-by-token parsing (read_line, lines, split) Yes (Required) Scanning for \n byte-by-byte without a buffer would issue 1 syscall per character!
Many small read_exact calls (binary headers, integers, msgpack/bincode) Yes Amortizes thousands of 2–8 byte field reads into a single 8 KiB syscall
Reading an entire file into memory at once (fs::read, fs::read_to_string) No fs::read queries the file size and reads directly into the target Vec<u8> without double-buffering
Reading into a large caller-managed buffer (>= 8 KiB chunks) No Calling file.read(&mut large_buf) directly avoids an extra memcpy through BufReader

Inspecting BufReader's Internal Buffer State

BufReader exposes its internal capacity (.capacity()), currently buffered unread slice (.buffer()), and underlying reader reference (.get_ref()):

use std::io::{self, BufReader, Cursor, Read};

fn main() -> io::Result<()> {
    let raw_data = b"HEADER:1234567890_PAYLOAD_BYTES";
    let mut reader = BufReader::with_capacity(16, Cursor::new(&raw_data[..]));

    println!(
        "initial -> capacity: {} B, buffered: {} B",
        reader.capacity(),
        reader.buffer().len()
    );

    // Reading 7 bytes triggers a 16-byte fill from the underlying source,
    // returning 7 bytes to `header` and leaving 9 unread bytes in `reader.buffer()`!
    let mut header = [0_u8; 7];
    reader.read_exact(&mut header)?;

    println!(
        "after 7B read -> header: {:?}, still buffered in RAM: {:?} ({} B)",
        std::str::from_utf8(&header).unwrap(),
        std::str::from_utf8(reader.buffer()).unwrap(),
        reader.buffer().len()
    );

    Ok(())
}
initial -> capacity: 16 B, buffered: 0 B
after 7B read -> header: "HEADER:", still buffered in RAM: "123456789" (9 B)