Skip to content

Rust File and IO

Rust provides powerful and safe file and IO operation capabilities. Through the std::fs and std::io modules in the standard library, you can perform various file operations, directory management, network IO, and other tasks. This tutorial will comprehensively introduce various methods and best practices for file and IO operations in Rust.

🎯 Learning Objectives

Through this tutorial, you will master:

  • File creation, reading, writing, and deletion operations
  • Directory creation, traversal, and management
  • Different IO operation methods and performance optimization
  • Error handling and exception handling
  • File permissions and metadata operations
  • Advanced IO operation techniques
  • Network IO and other IO types

📖 Basic IO Concepts

Characteristics of Rust IO

Rust's IO system has the following characteristics:

  • Safety: Compile-time checking to avoid common IO errors
  • Zero-cost abstraction: High-performance IO operations
  • Error handling: Mandatory error handling to prevent program crashes
  • Cross-platform: Unified API works across different operating systems
  • Async support: Support for async IO operations
ModuleDescription
std::fsFile system operations (files and directories)
std::ioGeneral IO operations and traits
std::pathFile path operations
std::envEnvironment variables and program arguments
std::netNetwork IO operations

📁 Basic File Operations

Multiple Ways to Read Files

rust
use std::fs;
use std::io::{self, Read};
use std::path::Path;

fn main() -> io::Result<()> {
    // Method 1: Read entire file as string at once
    println!("=== Method 1: Read entire file as string ===");
    match fs::read_to_string("example_file.txt") {
        Ok(content) => {
            println!("File content:\n{}", content);
        },
        Err(error) => {
            println!("Failed to read file: {}", error);

            // If file doesn't exist, create an example file
            let example_content = "Hello, this is an example file!\nSecond line\nThird line";
            fs::write("example_file.txt", example_content)?;
            println!("Example file created");

            // Try reading again
            let content = fs::read_to_string("example_file.txt")?;
            println!("File content:\n{}", content);
        }
    }

    // Method 2: Read file as byte array
    println!("\n=== Method 2: Read file as byte array ===");
    let byte_data = fs::read("example_file.txt")?;
    println!("File size: {} bytes", byte_data.len());
    println!("First 20 bytes: {:?}", &byte_data[..20.min(byte_data.len())]);

    // Method 3: Use File and Read trait
    println!("\n=== Method 3: Use File and Read trait ===");
    let mut file = fs::File::open("example_file.txt")?;
    let mut buffer = String::new();
    file.read_to_string(&mut buffer)?;
    println!("Content read through Read trait:\n{}", buffer);

    Ok(())
}

Multiple Ways to Write Files

rust
use std::fs::{self, File, OpenOptions};
use std::io::{self, Write, BufWriter};

fn main() -> io::Result<()> {
    println!("=== File Writing Examples ===");

    // Method 1: Write entire content at once (overwrite)
    println!("1. Overwrite with fs::write");
    let new_content = "This is new file content\nWritten with fs::write\n";
    fs::write("output_file.txt", new_content)?;
    println!("Written to file: output_file.txt");

    // Method 2: Append content to file
    println!("\n2. Append content to file");
    let mut file = OpenOptions::new()
        .create(true)      // Create if file doesn't exist
        .append(true)      // Append mode
        .open("output_file.txt")?;

    writeln!(file, "This is appended first line")?;
    writeln!(file, "This is appended second line")?;
    writeln!(file, "Current time: {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"))?;

    // Method 3: Use BufWriter to improve write performance
    println!("\n3. Batch write with BufWriter");
    let file = File::create("batch_output.txt")?;
    let mut writer = BufWriter::new(file);

    for i in 1..=1000 {
        writeln!(writer, "Line {} data: random number {}", i, rand::random::<u32>())?;
    }
    writer.flush()?; // Ensure all data is written to file

    println!("Written 1000 lines to batch_output.txt");

    // Method 4: Write different data types
    println!("\n4. Write different data types");
    let binary_data = vec![0u8, 1, 2, 3, 255, 128, 64];
    fs::write("binary_file.bin", &binary_data)?;

    // Read and verify binary data
    let read_binary = fs::read("binary_file.bin")?;
    println!("Written binary data: {:?}", binary_data);
    println!("Read binary data: {:?}", read_binary);
    println!("Data consistency check: {}", binary_data == read_binary);

    Ok(())
}

Basic File and Directory Operations

rust
use std::fs::{self, DirEntry};
use std::io;
use std::path::Path;

fn main() -> io::Result<()> {
    println!("=== Basic File and Directory Operations ===");

    // Create directory
    let dir_name = "test_directory";
    if !Path::new(dir_name).exists() {
        fs::create_dir(dir_name)?;
        println!("Created directory: {}", dir_name);
    } else {
        println!("Directory already exists: {}", dir_name);
    }

    // Create nested directory
    let nested_dir = "test_directory/subdirectory/deep_directory";
    fs::create_dir_all(nested_dir)?;
    println!("Created nested directory: {}", nested_dir);

    // Create files in directory
    for i in 1..=5 {
        let file_path = format!("test_directory/file{}.txt", i);
        let content = format!("This is content of file {}\nCreation time: {}", i, chrono::Local::now());
        fs::write(&file_path, content)?;
    }
    println!("Created 5 files in directory");

    // Traverse directory
    println!("\n=== Traverse directory contents ===");
    traverse_directory("test_directory")?;

    // Copy file
    println!("\n=== File Copy ===");
    let source_file = "test_directory/file1.txt";
    let target_file = "test_directory/file1_copy.txt";
    fs::copy(source_file, target_file)?;
    println!("Copied file: {} -> {}", source_file, target_file);

    // Rename file
    println!("\n=== File Rename ===");
    let old_name = "test_directory/file2.txt";
    let new_name = "test_directory/renamed_file2.txt";
    fs::rename(old_name, new_name)?;
    println!("Renamed file: {} -> {}", old_name, new_name);

    // Delete file
    println!("\n=== Delete File ===");
    let file_to_delete = "test_directory/file3.txt";
    if Path::new(file_to_delete).exists() {
        fs::remove_file(file_to_delete)?;
        println!("Deleted file: {}", file_to_delete);
    }

    // Get file metadata
    println!("\n=== File Metadata ===");
    get_file_info("test_directory/file1.txt")?;

    Ok(())
}

fn traverse_directory(dir_path: &str) -> io::Result<()> {
    println!("Contents of directory '{}':", dir_path);

    let entries = fs::read_dir(dir_path)?;

    for entry_result in entries {
        let entry = entry_result?;
        let path = entry.path();
        let metadata = entry.metadata()?;

        let type_str = if metadata.is_dir() {
            "[Directory]"
        } else if metadata.is_file() {
            "[File]"
        } else {
            "[Other]"
        };

        let size_str = if metadata.is_file() {
            format!(" ({} bytes)", metadata.len())
        } else {
            String::new()
        };

        println!("  {} {}{}", type_str, path.display(), size_str);

        // Recursively traverse subdirectories
        if metadata.is_dir() {
            if let Some(path_string) = path.to_str() {
                traverse_directory(path_string)?;
            }
        }
    }

    Ok(())
}

fn get_file_info(file_path: &str) -> io::Result<()> {
    let metadata = fs::metadata(file_path)?;

    println!("File info: {}", file_path);
    println!("  File size: {} bytes", metadata.len());
    println!("  Is file: {}", metadata.is_file());
    println!("  Is directory: {}", metadata.is_dir());
    println!("  Is read-only: {}", metadata.permissions().readonly());

    // Get modification time
    if let Ok(modified_time) = metadata.modified() {
        if let Ok(system_time) = modified_time.duration_since(std::time::UNIX_EPOCH) {
            let seconds = system_time.as_secs();
            println!("  Modification time: {} (Unix timestamp)", seconds);
        }
    }

    Ok(())
}

🚀 Advanced File Operations

Buffered IO and Performance Optimization

rust
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
use std::time::Instant;

fn main() -> io::Result<()> {
    println!("=== Buffered IO Performance Comparison ===");

    // Create test data
    create_large_test_file("large_file.txt", 100000)?;

    // Compare performance of different read methods
    compare_read_performance("large_file.txt")?;

    // Compare performance of different write methods
    compare_write_performance()?;

    // Demonstrate line-by-line reading
    println!("\n=== Line-by-line reading of large file ===");
    read_file_line_by_line("large_file.txt")?;

    Ok(())
}

fn create_large_test_file(filename: &str, line_count: usize) -> io::Result<()> {
    println!("Creating test file with {} lines...", line_count);

    let start_time = Instant::now();
    let file = File::create(filename)?;
    let mut writer = BufWriter::new(file);

    for i in 1..=line_count {
        writeln!(writer, "Line {} with some test data and random numbers: {}", i, rand::random::<u64>())?;
    }

    writer.flush()?;
    let elapsed = start_time.elapsed();
    println!("File creation complete, time taken: {:?}", elapsed);

    Ok(())
}

fn compare_read_performance(filename: &str) -> io::Result<()> {
    println!("\n=== Read Performance Comparison ===");

    // Method 1: Unbuffered read
    let start_time = Instant::now();
    let mut file = File::open(filename)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    let unbuffered_time = start_time.elapsed();
    println!("Unbuffered read time: {:?}, characters read: {}", unbuffered_time, content.len());

    // Method 2: Buffered read
    let start_time = Instant::now();
    let file = File::open(filename)?;
    let mut reader = BufReader::new(file);
    let mut content = String::new();
    reader.read_to_string(&mut content)?;
    let buffered_time = start_time.elapsed();
    println!("Buffered read time: {:?}, characters read: {}", buffered_time, content.len());

    // Method 3: One-time read
    let start_time = Instant::now();
    let content = std::fs::read_to_string(filename)?;
    let one_shot_time = start_time.elapsed();
    println!("One-time read time: {:?}, characters read: {}", one_shot_time, content.len());

    Ok(())
}

fn compare_write_performance() -> io::Result<()> {
    println!("\n=== Write Performance Comparison ===");
    let data_lines = 50000;

    // Method 1: Unbuffered write
    let start_time = Instant::now();
    let mut file = File::create("unbuffered_output.txt")?;
    for i in 1..=data_lines {
        writeln!(file, "Unbuffered write line {} data", i)?;
    }
    let unbuffered_time = start_time.elapsed();
    println!("Unbuffered write {} lines time: {:?}", data_lines, unbuffered_time);

    // Method 2: Buffered write
    let start_time = Instant::now();
    let file = File::create("buffered_output.txt")?;
    let mut writer = BufWriter::new(file);
    for i in 1..=data_lines {
        writeln!(writer, "Buffered write line {} data", i)?;
    }
    writer.flush()?;
    let buffered_time = start_time.elapsed();
    println!("Buffered write {} lines time: {:?}", data_lines, buffered_time);

    println!("Performance improvement factor: {:.2}x", unbuffered_time.as_nanos() as f64 / buffered_time.as_nanos() as f64);

    Ok(())
}

fn read_file_line_by_line(filename: &str) -> io::Result<()> {
    let file = File::open(filename)?;
    let reader = BufReader::new(file);

    let mut line_count = 0;
    let start_time = Instant::now();

    for (line_number, line_result) in reader.lines().enumerate() {
        let _line_content = line_result?;
        line_count += 1;

        // Only show first 5 lines and last 5 lines
        if line_number < 5 || line_number >= line_count - 5 {
            println!("Line {}: {}", line_number + 1, _line_content);
        } else if line_number == 5 {
            println!("... (skipping middle lines) ...");
        }
    }

    let elapsed = start_time.elapsed();
    println!("Line-by-line read complete, total lines: {}, time taken: {:?}", line_count, elapsed);

    Ok(())
}

Continue Learning: Next Chapter - Rust Collections and Strings

📚 Summary

This tutorial comprehensively introduced the core concepts and practical applications of Rust file and IO operations:

Main Content Review

  1. IO Basic Concepts: Understanding the characteristics and advantages of Rust's IO system
  2. Basic File Operations: Mastering file reading, writing, creation, and deletion
  3. Directory Management: Learning directory creation, traversal, and management
  4. Advanced IO Operations: Understanding buffered IO and performance optimization techniques
  5. Error Handling: Mastering comprehensive error handling strategies
  6. Path Operations: Learning path creation, analysis, and manipulation
  7. Network IO: Understanding basic network IO operations

Key Concepts Summary

Functional AreaMain APIUse Cases
File Readingfs::read_to_string(), fs::read()Read small files at once
File Writingfs::write(), File::create()Write small files at once
Buffered IOBufReader, BufWriterLarge files or high-frequency operations
Directory Operationsfs::create_dir(), fs::read_dir()Directory management and traversal
Path HandlingPath, PathBufCross-platform path operations
Error Handlingio::Result, ErrorKindRobust error handling

Advantages of Rust IO

  • Safety: Prevents common IO errors at compile time
  • Performance: Zero-cost abstraction and efficient buffering mechanisms
  • Cross-platform: Unified API works across different operating systems
  • Error Handling: Mandatory error handling mechanism

Best Practice Recommendations

Performance Optimization

  • Use BufReader and BufWriter for large files or high-frequency IO operations
  • Use fs::read_to_string() and fs::write() directly for small files
  • Use BufReader::lines() when reading large files line by line
  • Remember to call flush() to ensure data is written when doing batch writes

Error Handling

  • Always use Result type to handle return values of IO operations
  • Implement different error handling logic based on different ErrorKind types
  • Avoid using expect() and unwrap() in production environments
  • Consider using retry mechanisms for temporary errors

Path Handling

  • Use Path and PathBuf for cross-platform path operations
  • Avoid directly concatenating strings for paths
  • Use join() method to combine path components
  • Check path existence before operating on files

Important Notes

  • File IO operations can fail, always handle errors properly
  • Pay attention to memory usage when working with large files
  • Be aware of differences in path separators when developing cross-platform
  • File permission issues may vary across different operating systems

Next Learning Steps

  1. Async IO: Learn file IO operations with async runtimes like tokio
  2. Network Programming: Deep dive into TCP/UDP network programming
  3. Data Serialization: Learn processing of data formats like JSON, TOML
  4. Databases: Learn using Rust to connect and operate databases

Through studying this tutorial, you should now be able to:

  • Perform file and directory operations safely and efficiently
  • Correctly handle errors in IO operations
  • Use appropriate IO methods for performance optimization
  • Perform cross-platform path handling
  • Apply Rust's IO capabilities in real projects

Rust's file and IO system provides you with powerful tools to build high-performance, reliable applications.

Content is for learning and research only.