Rust - File Input/ Output
Rust - File Input/Output (Modern Edition)
Section titled “Rust - File Input/Output (Modern Edition)”Modern applications frequently need to interact with the filesystem. Rust provides a robust and safe standard library module, std::fs, for handling file input and output (I/O). This tutorial covers the essential, modern practices for working with files in Rust.
Core Concepts: Result and Error Handling
Section titled “Core Concepts: Result and Error Handling”Nearly every file operation can fail for reasons outside your program’s control (e.g., file not found, permissions denied). Rust represents these outcomes using the Result<T, E> enum. Instead of panicking with .unwrap() or .expect(), modern Rust code propagates errors using the ? operator. This makes the code cleaner and more robust.
To use the ? operator, the enclosing function must return a Result type. For main, a common signature is fn main() -> std::io::Result<()>, which indicates it can return any standard I/O error.
Common File Operations
Section titled “Common File Operations”The std::fs module offers both high-level convenience functions and lower-level control via the File struct.
| No. | Function / Struct | Signature (Simplified) | Description |
|---|---|---|---|
| 1 | std::fs::write | fn write<P, C>(path: P, contents: C) -> io::Result<()> | A convenience function to open a file, write all contents, and close it. Creates the file if it doesn’t exist, and overwrites it if it does. |
| 2 | std::fs::read_to_string | fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> | A convenience function to open a file, read its entire contents into a String, and close it. |
| 3 | std::fs::File::create | fn create<P: AsRef<Path>>(path: P) -> io::Result<File> | Opens a file in write-only mode. Creates a new file or truncates an existing one. Returns a File handle for more complex operations. |
| 4 | std::fs::File::open | fn open<P: AsRef<Path>>(path: P) -> io::Result<File> | Opens an existing file in read-only mode. Returns a File handle. |
| 5 | std::fs::OpenOptions | struct OpenOptions | Provides detailed control over how a file is opened (read, write, append, create, etc.). This is the most flexible approach. |
| 6 | std::fs::remove_file | fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> | Removes a file from the filesystem. |
| 7 | std::io::Write::write_all | fn write_all(&mut self, buf: &[u8]) -> io::Result<()> | A trait method on File that attempts to write an entire buffer of bytes. |
| 8 | std::io::Read::read_to_string | fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> | A trait method on File that reads all bytes until EOF into a string buffer. |
Example 1: Simple Write and Read
Section titled “Example 1: Simple Write and Read”Using the high-level functions fs::write and fs::read_to_string is the most straightforward way for simple tasks. Notice the main function’s return type.
use std::fs;use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> { let file_path = "greeting.txt"; let content = "Hello, Modern Rust!\nThis is a new line.";
// Write content to a file, overwriting if it exists fs::write(file_path, content)?; println!("Successfully wrote to {}", file_path);
// Read the entire file into a string let read_content = fs::read_to_string(file_path)?; println!("\n--- Reading from file ---"); print!("{}", read_content);
Ok(())}Output
Section titled “Output”Successfully wrote to greeting.txt
--- Reading from file ---Hello, Modern Rust!This is a new line.Example 2: Appending to a File
Section titled “Example 2: Appending to a File”To append content without overwriting, we use OpenOptions. We chain methods to configure the desired behavior and then call .open().
use std::fs::OpenOptions;use std::io::Write;
fn main() -> std::io::Result<()> { let file_path = "log.txt";
let mut file = OpenOptions::new() .append(true) // Set append mode .create(true) // Create the file if it doesn't exist .open(file_path)?;
file.write_all(b"LOG: Application started.\n")?; println!("Successfully appended to log file.");
Ok(())}Example 3: Deleting and Copying a File
Section titled “Example 3: Deleting and Copying a File”The std::fs module also includes simple functions for common filesystem management tasks.
use std::fs;
fn main() -> std::io::Result<()> { // First, let's ensure we have a file to work with fs::write("source.txt", "This is the original file.")?;
// Copy the file fs::copy("source.txt", "destination.txt")?; println!("Copied source.txt to destination.txt");
// Read the new file to verify let content = fs::read_to_string("destination.txt")?; println!("Content of destination.txt: '{}'", content);
// Delete the original file fs::remove_file("source.txt")?; println!("Removed source.txt");
Ok(())}Best Practice: Buffered I/O for Performance
Section titled “Best Practice: Buffered I/O for Performance”When reading or writing large files, making many small system calls can be inefficient. Rust’s std::io module provides BufReader and BufWriter to buffer operations in memory, reducing system calls and improving performance.
use std::fs::File;use std::io::{Write, BufWriter};
fn main() -> std::io::Result<()> { let file = File::create("large_log.txt")?; let mut writer = BufWriter::new(file);
for i in 0..1000 { writeln!(writer, "Entry number {}", i)?; }
// The buffer is automatically flushed when `writer` goes out of scope. println!("Wrote 1000 entries with a BufWriter.");
Ok(())}