File Input/Output in Rust

About This Module

This module covers file input/output operations in Rust, focusing on reading from and writing to files safely and efficiently. Students will learn how to handle files, work with the std::fs and std::io modules, and implement error handling for file operations. The module includes practical examples of reading and writing different data formats, with a focus on processing large datasets commonly used in data science applications.

Prework

Before this lecture, please read:

Pre-lecture Reflections

  1. How does Rust's ownership model affect file handling compared to other languages?
  2. What are the advantages of Rust's Result type for file I/O error handling?
  3. When would you use buffered vs. unbuffered file reading in data processing applications?

Lecture

Learning Objectives

By the end of this lecture, you should be able to:

  • Open, read from, and write to files in Rust using std::fs and std::io
  • Handle file I/O errors properly using Result types and expect()
  • Use buffered readers for efficient file processing
  • Parse and generate file formats commonly used in data science
  • Implement file-based data processing workflows

File I/O

See also file_io.

:dep rand="0.8.5"

use std::fs::File;        // object providing access to an open file on filesystem
use std::io::prelude::*;  // imports common I/O traits
use rand::Rng;


fn generate_file(path: &str, n: usize) {
    // Generate a random file of edges for vertices 0.n

    // Create a file at `path` and return Result<File> or error
    // .expect() unwraps the Result<File> or prints error and panics
    let mut file = File::create(path).expect("Unable to create file");
    
    for i in 0..n {
        // How many neighbors will this node have
        let rng = rand::thread_rng().gen_range(0..20) as usize;
        
        for _j in 0..rng {
            // Randomly select a neighbor (even with duplicates but not to ourselves)
            let neighbor = rand::thread_rng().gen_range(0..n) as usize;
            if neighbor != i {
                let s: String = format!("{} {}\n", i, neighbor);
                file.write_all(s.as_bytes()).expect("Unable to write file");
            }
        }
    }
}

fn read_file(path: &str) -> Vec<(u32, u32)> {
    let mut result: Vec<(u32, u32)> = Vec::new();
    let file = File::open(path).expect("Could not open file");
    let buf_reader = std::io::BufReader::new(file).lines();
    for line in buf_reader {
        let line_str = line.expect("Error reading");
        let v: Vec<&str> = line_str.trim().split(' ').collect();
        let x = v[0].parse::<u32>().unwrap();
        let y = v[1].parse::<u32>().unwrap();
        result.push((x, y));
    }
    return result;
}

println!("Generating file");
generate_file("list_of_edges.txt", 10);
let edges = read_file("list_of_edges.txt");
println!("{:?}", edges);
Generating file
[(0, 5), (0, 9), (0, 1), (0, 8), (0, 2), (0, 2), (0, 1), (0, 6), (0, 8), (0, 9), (1, 6), (1, 6), (1, 4), (2, 7), (2, 7), (2, 1), (2, 1), (2, 8), (2, 6), (3, 0), (3, 1), (3, 2), (3, 8), (3, 2), (3, 8), (3, 0), (3, 4), (3, 8), (3, 7), (3, 8), (3, 0), (3, 8), (3, 2), (3, 6), (3, 8), (3, 4), (4, 7), (4, 2), (4, 0), (4, 5), (4, 0), (4, 9), (4, 2), (4, 2), (4, 9), (4, 7), (4, 3), (4, 9), (4, 9), (5, 8), (5, 0), (5, 3), (5, 4), (5, 2), (5, 4), (5, 9), (6, 3), (6, 7), (6, 9), (6, 3), (6, 7), (6, 7), (6, 4), (6, 0), (6, 3), (6, 8), (6, 0), (6, 9), (6, 5), (7, 4), (7, 9), (7, 3), (7, 6), (7, 3), (7, 9), (7, 1), (7, 8), (7, 1), (7, 2), (7, 2), (8, 2), (8, 0), (8, 1), (8, 3), (8, 7), (8, 5), (8, 4), (9, 2), (9, 3), (9, 4), (9, 5), (9, 4), (9, 2), (9, 5), (9, 2)]