Skip to content

Rust - File Input/ Output

Modern applications frequently need to interact with the filesystem. Rust provides a robust and safe standard library module, std::fs, for handling file input and output (I/O). This tutorial covers the essential, modern practices for working with files in Rust.

Nearly every file operation can fail for reasons outside your program’s control (e.g., file not found, permissions denied). Rust represents these outcomes using the Result<T, E> enum. Instead of panicking with .unwrap() or .expect(), modern Rust code propagates errors using the ? operator. This makes the code cleaner and more robust.

To use the ? operator, the enclosing function must return a Result type. For main, a common signature is fn main() -> std::io::Result<()>, which indicates it can return any standard I/O error.

The std::fs module offers both high-level convenience functions and lower-level control via the File struct.

No.Function / StructSignature (Simplified)Description
1std::fs::writefn write<P, C>(path: P, contents: C) -> io::Result<()>A convenience function to open a file, write all contents, and close it. Creates the file if it doesn’t exist, and overwrites it if it does.
2std::fs::read_to_stringfn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String>A convenience function to open a file, read its entire contents into a String, and close it.
3std::fs::File::createfn create<P: AsRef<Path>>(path: P) -> io::Result<File>Opens a file in write-only mode. Creates a new file or truncates an existing one. Returns a File handle for more complex operations.
4std::fs::File::openfn open<P: AsRef<Path>>(path: P) -> io::Result<File>Opens an existing file in read-only mode. Returns a File handle.
5std::fs::OpenOptionsstruct OpenOptionsProvides detailed control over how a file is opened (read, write, append, create, etc.). This is the most flexible approach.
6std::fs::remove_filefn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()>Removes a file from the filesystem.
7std::io::Write::write_allfn write_all(&mut self, buf: &[u8]) -> io::Result<()>A trait method on File that attempts to write an entire buffer of bytes.
8std::io::Read::read_to_stringfn read_to_string(&mut self, buf: &mut String) -> io::Result<usize>A trait method on File that reads all bytes until EOF into a string buffer.

Using the high-level functions fs::write and fs::read_to_string is the most straightforward way for simple tasks. Notice the main function’s return type.

use std::fs;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let file_path = "greeting.txt";
let content = "Hello, Modern Rust!\nThis is a new line.";
// Write content to a file, overwriting if it exists
fs::write(file_path, content)?;
println!("Successfully wrote to {}", file_path);
// Read the entire file into a string
let read_content = fs::read_to_string(file_path)?;
println!("\n--- Reading from file ---");
print!("{}", read_content);
Ok(())
}
Successfully wrote to greeting.txt
--- Reading from file ---
Hello, Modern Rust!
This is a new line.

To append content without overwriting, we use OpenOptions. We chain methods to configure the desired behavior and then call .open().

use std::fs::OpenOptions;
use std::io::Write;
fn main() -> std::io::Result<()> {
let file_path = "log.txt";
let mut file = OpenOptions::new()
.append(true) // Set append mode
.create(true) // Create the file if it doesn't exist
.open(file_path)?;
file.write_all(b"LOG: Application started.\n")?;
println!("Successfully appended to log file.");
Ok(())
}

The std::fs module also includes simple functions for common filesystem management tasks.

use std::fs;
fn main() -> std::io::Result<()> {
// First, let's ensure we have a file to work with
fs::write("source.txt", "This is the original file.")?;
// Copy the file
fs::copy("source.txt", "destination.txt")?;
println!("Copied source.txt to destination.txt");
// Read the new file to verify
let content = fs::read_to_string("destination.txt")?;
println!("Content of destination.txt: '{}'", content);
// Delete the original file
fs::remove_file("source.txt")?;
println!("Removed source.txt");
Ok(())
}

Best Practice: Buffered I/O for Performance

Section titled “Best Practice: Buffered I/O for Performance”

When reading or writing large files, making many small system calls can be inefficient. Rust’s std::io module provides BufReader and BufWriter to buffer operations in memory, reducing system calls and improving performance.

use std::fs::File;
use std::io::{Write, BufWriter};
fn main() -> std::io::Result<()> {
let file = File::create("large_log.txt")?;
let mut writer = BufWriter::new(file);
for i in 0..1000 {
writeln!(writer, "Entry number {}", i)?;
}
// The buffer is automatically flushed when `writer` goes out of scope.
println!("Wrote 1000 entries with a BufWriter.");
Ok(())
}