Rust File Handling

Programs save settings, store results, and read data from files. Rust's standard library offers tools for each task, and every operation returns a Result because disks fail, files go missing, and permissions block access. Treat file work like using a filing cabinet: open the drawer, read or write a page, and close the drawer.

Reading a Whole File

The function fs::read_to_string loads an entire text file into a String.

use std::fs;

fn main() {
    match fs::read_to_string("notes.txt") {
        Ok(content) => println!("{content}"),
        Err(e) => println!("Cannot read file: {e}"),
    }
}

This approach suits small and medium files. Huge files deserve line-by-line reading, covered below.

Writing a Whole File

The function fs::write creates a file or replaces the content of an existing one.

use std::fs;

fn main() -> std::io::Result<()> {
    fs::write("greeting.txt", "Hello, file!")?;
    println!("File saved");
    Ok(())
}

Existing content disappears without a warning, so choose file names with care.

The File Type

Use File when you need more control than the one-line helpers give.

use std::fs::File;
use std::io::Write;

fn main() -> std::io::Result<()> {
    let mut file = File::create("log.txt")?;
    writeln!(file, "Line one")?;
    writeln!(file, "Line two")?;
    Ok(())
}
 File::create --> file handle --> writeln! --> data on disk
                                    ^
                         file closes automatically when
                         the handle leaves scope

Appending to a File

The OpenOptions builder lets you pick the mode. The append option adds text to the end and keeps existing lines.

use std::fs::OpenOptions;
use std::io::Write;

fn main() -> std::io::Result<()> {
    let mut file = OpenOptions::new()
        .create(true)
        .append(true)
        .open("log.txt")?;
    writeln!(file, "New entry")?;
    Ok(())
}
OptionEffect
read(true)Allows reading
write(true)Allows writing
append(true)Adds to the end
create(true)Makes the file when missing
truncate(true)Clears old content on open

Reading Line by Line

A BufReader collects chunks of data in memory and hands out one line at a time. Memory use stays low even for very large files.

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

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

    for (number, line) in reader.lines().enumerate() {
        println!("{}: {}", number + 1, line?);
    }
    Ok(())
}
1: Line one
2: Line two
3: New entry

Working with Paths

The Path and PathBuf types describe file locations in a way that works across Windows, macOS, and Linux.

use std::path::Path;

fn main() {
    let path = Path::new("data").join("report.txt");
    println!("{}", path.display());
    println!("{}", path.exists());
    println!("{:?}", path.extension());
}

The join method inserts the correct separator for the operating system, so avoid typing slashes by hand.

Folders and Cleanup

use std::fs;

fn main() -> std::io::Result<()> {
    fs::create_dir_all("output/reports")?;     // make folders
    fs::copy("log.txt", "output/log_copy.txt")?;
    fs::rename("output/log_copy.txt", "output/log_old.txt")?;
    fs::remove_file("output/log_old.txt")?;     // delete a file

    for entry in fs::read_dir("output")? {
        let entry = entry?;
        println!("{}", entry.file_name().to_string_lossy());
    }
    Ok(())
}

Handling Missing Files Gracefully

The error value reveals the cause. A program can react differently to a missing file than to a permission problem.

use std::fs;
use std::io::ErrorKind;

fn main() {
    match fs::read_to_string("settings.txt") {
        Ok(text) => println!("Loaded {} bytes", text.len()),
        Err(e) if e.kind() == ErrorKind::NotFound => {
            println!("No settings file found, using defaults");
        }
        Err(e) => println!("Unexpected problem: {e}"),
    }
}

A Small Complete Program

The program below counts the words in a file.

use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let text = fs::read_to_string("notes.txt")?;
    let words = text.split_whitespace().count();
    let lines = text.lines().count();
    println!("{lines} lines, {words} words");
    Ok(())
}

Safety and Privacy Tips

  • Never trust file names typed by outside users without checking them.
  • Avoid saving passwords or private details in plain text files.
  • Write to a temporary file, rename it afterward, so a crash cannot leave half-written data.
  • Check that the target folder exists before writing.

Practice Ideas

  • Write a program that saves a list of names to a file and loads them back.
  • Build a simple diary that appends a line each time it runs.
  • Count the lines that contain a chosen word in a text file.

Choosing the Right Tool

Pick the shortest helper that fits the job. Whole-file helpers suit configuration files and notes. A buffered reader suits logs and large data sets. The builder type suits special modes such as appending. Matching the tool to the task keeps code short and memory use low.

Key Points

  • fs::read_to_string and fs::write handle whole files in one call.
  • File, OpenOptions, and BufReader give finer control.
  • Every file action returns a Result, so handle failures with match or ?.
  • Use Path for portable file locations.