Reading and Writing Files

File reading and writing happens primarily through the std::fs module of the standard library, and the traits found in std::io, like Read and Write.

Read from and Write to a File

std cat~filesystem

The following example opens a file, writes to it, then reads its contents back in several different ways.

  • std::fs::File::create opens a std::fs::File for writing, std::fs::File::open for reading. std::fs::OpenOptions may be used to open a file in another mode.
  • File implements the std::io::Read trait for reading bytes from a source, the std::io::Write trait for writing bytes to a destination, and std::io::Seek for moving within a stream of bytes.
  • You may use the std::write! and writeln! macros to write formatted data.
  • File does not buffer reads and writes. Wrap the File in a std::io::BufReader or BufWriter (which implement BufRead and BufWrite), when performing many small read or write calls.
  • std::io::BufRead::lines returns a std::io::Lines iterator over individual lines of the file. LF or CRLF line endings are removed.
  • std::fs::write writes a slice as the entire contents of a file. This is a convenience function for using File::create and std::io::Write::write_all with fewer imports.
  • std::fs::read and std::fs::read_to_string read the entire contents of a file into a bytes vector or a string, respectively. They are convenience functions for using File::open and std::io::Read::read_to_end or read_to_string without an intermediate variable.
//! Demonstrates writing to and reading from a file.

use std::fs;
use std::fs::File;
use std::fs::OpenOptions;
use std::io;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;

fn write_to_file() -> Result<(), io::Error> {
    // The simplest way is to write a slice as the entire contents of a file
    // with `fs::write`. This function will create a file if it does not
    // exist, and will entirely replace its contents if it does.
    let path_str = "temp/lines.txt";
    fs::write(path_str, b"Lorem ipsum")?;

    // Or open a file in write-only mode, then write.
    // `create` creates a file if it does not exist, and truncates it if it
    // does:
    let mut output = File::create(path_str)?;

    // Write an entire buffer to the file:
    output.write_all(b"Writing some bytes.\n")?;
    // Or write a formatted string to the file using e.g. the `write!` macro:
    let text = "Rust 💖 Fun\n";
    write!(&mut output, "Writing: {text}")?;

    // Or open the file in append mode:
    let mut file = OpenOptions::new()
        .append(true) // Set the append mode to true.
        .create(true) // Create the file if it doesn't exist.
        .open(path_str)?;

    // `writeln!` writes a (plain or formatted) string followed by a newline:
    writeln!(file, "This is a new line appended to the file.")?;
    Ok(())
}

fn read_from_file() -> Result<(), io::Error> {
    let path_str = "temp/lines.txt";

    // Read the entire contents of a file into a string.
    // The contents of the file must be valid UTF-8.
    let message: String = fs::read_to_string(path_str)?;
    println!("`read_to_string`:\n {message}");

    // Or read the entire contents of a file into a bytes vector:
    let data: Vec<u8> = fs::read("temp/lines.txt")?;
    assert_eq!(data[0..3], [87, 114, 105]);

    // Or open the file for reading:
    let input = File::open(path_str)?;
    // Call a method of the `Read` trait,
    // or add buffering:
    let buffered = BufReader::new(input);
    // Iterate over the lines of this reader.
    // The `?` operator propagates the error if one occurs.
    for line in buffered.lines() {
        println!("{}", line?);
    }
    Ok(())
}

fn main() -> Result<(), io::Error> {
    write_to_file()?;
    read_from_file()?;
    Ok(())
}

Avoid Writing and Reading from the Same File

same-file cat~filesystem

This example demonstrates how to check whether two files refer to the same file or directory, using same-file::Handle. Use this technique to check whether the standard input and standard output (or error) of a process are the same (due to piping / redirection) and avoid reading and writing from the same file. You may also use same-file check whether two file paths correspond to the same file:

use std::io::Error;

use same_file::Handle;

/// Check whether we are reading & writing to the same file.
fn main() -> Result<(), Error> {
    // Get a `Handle` for the standard input, output, and error:
    let stdin = Handle::stdin()?;
    let stdout = Handle::stdout()?;
    let stderr = Handle::stderr()?;

    // Check if the standard input is the same as the standard output or error
    // handle:
    if (stdin == stdout) | (stdin == stderr) {
        println!("You are reading and writing to the same file!");
        // Return an error to exit the process:
        // return Err(Error::other(
        //     "You are reading and writing to the same file!",
        // ));
    }
    if stdout == stderr {
        println!("stdout == stderr");
    }

    // Use `from_path` to get a `Handle` for a file.
    // Note that the underlying `File` is opened in read-only mode on all
    // platforms:
    // let path_to_read = Path::new("temp/new.txt");
    // let _handle = Handle::from_path(path_to_read)?;

    // You may also check whether two file paths correspond to the same file
    // (due to hard or symbolic links):
    #[cfg(unix)]
    assert!(same_file::is_same_file("/bin/sh", "/usr/bin/sh")?);

    Ok(())
}

Access a File Randomly Using a Memory Map

memmap2 cat~filesystem

In the following example, we create a memory map of a file using memmap2 and simulate some non-sequential reads from the file. Using a memory map simplifies navigation of a std::fs::File - by indexing into a slice rather than dealing with std::fs::File::seek↗.Memory mapping may also enhance performance in applications that require large data processing or frequent file access.

Note that the memmap2::Mmap::map function assumes the file behind the memory map is not being modified at the same time by another process or else a race condition↗ may occur.

It is also possible to write to a File as if it were a &mut [u8] by using mutable memory maps.

//! Demonstrates reading from a memory-mapped file.

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

use memmap2::Mmap;
use memmap2::MmapMut;
use memmap2::MmapOptions;

fn main() -> Result<(), Error> {
    write!(
        File::create("temp/content.txt")?,
        "My hovercraft is full of eels!"
    )?;
    // Open the file for reading:
    let file = File::open("temp/content.txt")?;
    // Create a read-only memory map of the file. This is unsafe because it
    // relies on the file not being modified externally:
    let map = unsafe { Mmap::map(&file)? };
    // `Mmap` is `Deref<Target = [u8]>` and therefore can be used like a slice.
    // Assert that a slice of the memory map matches the expected bytes:
    assert_eq!(&map[3..13], b"hovercraft");
    // Collect bytes from the memory map at random indexes:
    let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29];
    let random_bytes: Vec<u8> =
        random_indexes.iter().map(|&idx| map[idx]).collect();
    assert_eq!(&random_bytes[..], b"My loaf!");
    // A file-backed memory map remains valid even after the File is dropped.

    // You also create mutable memory-mapped buffers - either file-backed or
    // anonymous - with `MmapMut`.
    // - A file-backed buffer may be used to read from or write to a file.
    // - An anonymous buffer may be used any place that an in-memory byte buffer
    //   is needed.
    let mut mmap_mut: MmapMut = MmapOptions::new().len(13).map_anon()?;
    mmap_mut.copy_from_slice(b"Hello, world!");

    Ok(())
}
  • File Watching.
  • Paths.
  • Standard Input and Output.
  • Temporary Files.