Reading and Writing Files in Rust 1.x: De Facto Approaches

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Rust and Files: Myths, Fears, and De Facto Approaches

Have you ever asked yourself: "What is the correct way to read and write files in modern Rust?". If so, you are not alone. On Stack Overflow, the question "What's the de-facto way of reading and writing files in Rust 1.x?" has gathered thousands of views and dozens of answers. This is a real pain point for the community, especially for those coming from Python or JavaScript, where open() and read() are basic, while in Rust you have to deal with ownership, borrowing, and a bunch of traits.

In this article, we will break down all the main ways to work with files in Rust 1.x: from "just read everything into a string" to efficient line-by-line reading of large logs. You will learn which approach is considered idiomatic and which is better reserved for special cases. Let's go!



1. std::fs: The Standard Arsenal

The std::fs module is your first stop. It provides functions for reading, writing, copying, and deleting files. Rust 1.x stabilized convenient wrappers that hide most of the low-level work with system calls.



Reading an Entire File into a String

The simplest way is to use std::fs::read_to_string. It opens a file, reads it entirely into memory, and returns Result<String>. Perfect for small configs or text files.

use std::fs;

fn main() -> std::io::Result<()> { let content = fs::read_to_string("hello.txt")?; println!("File contains:{}", content); Ok(())}

This method is the de facto standard for simple cases. It is concise, safe, and uses ? for error handling. The only downside is that the entire file is loaded into memory, so it is not suitable for gigabyte-sized logs.



Reading into a Byte Vector

If you are working with binary data (images, archives), use std::fs::read. It returns Vec<u8>.

use std::fs;

fn main() -> std::io::Result<()> { let bytes = fs::read("image.png")?; println!("Read {} bytes", bytes.len()); Ok(())}


Writing to a File

For writing, use std::fs::write. It takes a path and data (a string or byte array) and creates/overwrites the file.

use std::fs;

fn main() -> std::io::Result<()> { fs::write("output.txt", "Hello, Rust!")?; Ok(())}

Simple as can be. But if you need to append to the end of a file instead of overwriting it, you will have to use OpenOptions.



2. BufReader and BufWriter: When Size Matters

For large files or frequent I/O operations, buffered wrappers come to the rescue. They reduce the number of system calls, which is critical for performance.



Line-by-Line Reading with BufReader

A classic example is reading a large log file line by line:

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

fn main() -> std::io::Result<()> { let file = File::open("large_log.txt")?; let reader = BufReader::new(file);

for line in reader.lines() { let line = line?; println!("{}", line); } Ok(())}

The lines() method returns an iterator of lines, each of which is a Result<String>. Note the line? inside the loop — this handles read errors for each line.

Important: lines() removes newline characters. If you need to preserve them, use read_until or read the file as Vec<u8>.



Writing with BufWriter

When writing large amounts of data, buffering is also useful:

use std::fs::File;use std::io::{BufWriter, Write};

fn main() -> std::io::Result<

Blogs

Book Recommendations