File System Operations
Reading Files
Rust provides three primary patterns for reading files, depending on the file's size and structure: whole-file reading (fs::read / fs::read_to_string), buffered streaming (BufReader::new(file)), and random-access / fixed-chunk reading (Read::read_exact + Seek).
Choosing the Right File Reading Strategy
How large is the file and how is it structured?
|
+-- Fits comfortably in RAM & needed all at once (configs, JSON, source files):
| +-- Text (UTF-8) -------------> `std::fs::read_to_string(path)`
| +-- Binary bytes -------------> `std::fs::read(path)`
|
+-- Line-oriented or large streaming file (logs, CSV, multi-GB datasets):
| +-----------------------------> `BufReader::new(File::open(path)?)` + `read_line`
|
+-- Indexed binary file (SQLite pages, ZIP central directory, media headers):
+-----------------------------> `File::open(path)?` + `Seek::seek` + `read_exact`
| Pattern | Memory Footprint | OS Syscalls | Best Use Case |
|---|---|---|---|
fs::read_to_string(path) |
$O(\text{file size})$ (1 allocation) |
Minimal (open, fstat, read, close) |
Config files, templates, small inputs |
fs::read(path) |
$O(\text{file size})$ (1 allocation) |
Minimal | Images, WASM modules, binary blobs |
BufReader::new(file) + read_line |
$O(\text{longest line})$ (8 KiB buffer) |
1 syscall per 8 KiB |
Multi-gigabyte log files and line streams |
file.seek(...) + file.read_exact(...) |
$O(\text{record size})$ | lseek + read per record |
Reading a specific byte offset without loading the whole file |
Random-Access Reading with Seek and read_exact
Every std::fs::File implements std::io::Seek (seek(SeekFrom::Start(pos)), seek(SeekFrom::End(offset)), seek(SeekFrom::Current(delta)), rewind(), and stream_position()). This allows jumping directly to a footer or indexed record inside a large binary file without reading the preceding bytes:
use std::fs::{self, File};
use std::io::{self, Read, Seek, SeekFrom, Write};
fn main() -> io::Result<()> {
let path = std::env::temp_dir().join("rust_ch50_seek_demo.bin");
// Write a 16-byte binary file where the last 4 bytes store a u32 footer magic:
{
let mut f = File::create(&path)?;
f.write_all(b"PAYLOAD_1234")?;
f.write_all(&0xCAFE_BABE_u32.to_be_bytes())?;
}
let mut file = File::open(&path)?;
// Jump directly to 4 bytes before the end of the file (`SeekFrom::End(-4)`):
let footer_pos = file.seek(SeekFrom::End(-4))?;
let mut magic_bytes = [0_u8; 4];
file.read_exact(&mut magic_bytes)?;
let magic = u32::from_be_bytes(magic_bytes);
// Rewind back to byte 0 (`file.rewind()`) to read the first 7 bytes:
file.rewind()?;
let mut prefix = [0_u8; 7];
file.read_exact(&mut prefix)?;
fs::remove_file(&path)?;
println!(
"footer at offset {}: {:#010x}, prefix: {:?}",
footer_pos,
magic,
std::str::from_utf8(&prefix).unwrap()
);
Ok(())
}
footer at offset 12: 0xcafebabe, prefix: "PAYLOAD"