Practical File Reading in Rust

Reading files is an essential operation and a necessary skill to master in our daily development. How to read files, how to write files, how to close connections after writing files, how to create files, and so on.

Reading Files and Iterating Through Their Lines

When we need to read a file line by line in our project, how should we handle it, or how does Rust handle it? File I/O has several very important components including Write, BufReader, BufRead, Read, etc. Let’s explain file reading and writing in Rust through a simple example:

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

fn main() -> Result<(), Error> {
    let path = "lines.txt";

    // Create file
    let mut output = File::create(path)?;
    // Write three lines of content
    write!(output, "Rust\nšŸ’–\nFun")?;

    let input = File::open(path)?;
    let buffered = BufReader::new(input);

    // Iterate through each line in the file, line is a string
    for line in buffered.lines() {
        println!("{}", line?);
    }

    Ok(())
}

Sometimes, when dealing with a large number of files, we may encounter multiple read/write operations on the same file because we are not sure whether the contents of the same filename are consistent. Fortunately, same_file can help us determine whether two files are the same.

Preventing Read/Write on the Same File

use same_file::Handle;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::path::Path;

fn main() -> Result<(), Error> {
    let path_to_read = Path::new("new.txt");

    // Get the filename to write from standard output
    let stdout_handle = Handle::stdout()?;
    // Compare the filename to be written with the filename to be read
    let handle = Handle::from_path(path_to_read)?;

    if stdout_handle == handle {
        return Err(Error::new(
            ErrorKind::Other,
            "You are reading and writing to the same file",
        ));
    } else {
        let file = File::open(&path_to_read)?;
        let file = BufReader::new(file);
        for (num, line) in file.lines().enumerate() {
            println!("{} : {}", num, line?.to_uppercase());
        }
    }

    Ok(())
}

memmap can create a memory mapping of a file, allowing for some non-sequential reads. Using memory mapping means loading relevant indexes into memory rather than accessing the file through seek. The Mmap::map function assumes that the file to be mapped will not be modified by other processes simultaneously.

Accessing Files Using Memory Mapping

use memmap::Mmap;
use std::fs::File;
use std::io::{Write, Error};

fn main() -> Result<(), Error> {
    write!(File::create("content.txt")?, "My hovercraft is full of eels!")?;

    let file = File::open("content.txt")?;
    let map = unsafe { Mmap::map(&file)? };

    let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29];
    assert_eq!(&map[3..13], b"hovercraft");
    let random_bytes: Vec<u8> = random_indexes.iter()
        .map(|&idx| map[idx])
        .collect();
    assert_eq!(&random_bytes[..], b"My loaf!");
    Ok(())
}
</u8>

Leave a Comment