#17 File I/O
std::fs, read, write
Reading files
The simplest way to read a file is std::fs::read_to_string(). It reads the entire file into a String:
use std::fs;
fn main() {
match fs::read_to_string("hello.txt") {
Ok(content) => println!("File content: {}", content),
Err(e) => println!("Error reading file: {}", e),
}
}For more control, use File::open() with a BufReader to read line by line:
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn read_lines(path: &str) -> io::Result<Vec<String>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut lines = Vec::new();
for line in reader.lines() {
lines.push(line?);
}
Ok(lines)
}
fn main() {
match read_lines("data.txt") {
Ok(lines) => {
println!("Lines in file: {}", lines.len());
for line in &lines {
println!(" {}", line);
}
}
Err(e) => println!("Error: {}", e),
}
}Writing files
Use fs::write() for a quick write, or File::create() for more control. Both will create the file if it does not exist and overwrite it if it does:
use std::fs;
use std::io::Write;
fn main() -> std::io::Result<()> {
// Simple: write entire content at once
fs::write("output.txt", "Hello, Rust!\nThis is line 2.")?;
println!("Wrote to output.txt");
// More control: use File::create
let mut file = fs::File::create("formatted.txt")?;
writeln!(file, "Name: {}", "Rust")?;
writeln!(file, "Version: {}", "1.75")?;
writeln!(file, "Year: {}", 2024)?;
Ok(())
}Appending to files
To add content to an existing file without overwriting, use OpenOptions:
use std::fs::OpenOptions;
use std::io::Write;
fn append_log(message: &str) -> std::io::Result<()> {
let mut file = OpenOptions::new()
.create(true) // Create if it does not exist
.append(true) // Append instead of overwrite
.open("log.txt")?;
writeln!(file, "[LOG] {}", message)?;
Ok(())
}
fn main() -> std::io::Result<()> {
append_log("Application started")?;
append_log("Processing data")?;
append_log("Done")?;
println!("Appended to log.txt");
Ok(())
}Working with paths
The std::path module provides Path and PathBuf for cross-platform path manipulation:
use std::path::{Path, PathBuf};
fn main() {
let path = Path::new("src/main.rs");
println!("Exists: {}", path.exists());
println!("Is file: {}", path.is_file());
println!("Extension: {:?}", path.extension());
println!("File name: {:?}", path.file_name());
println!("Parent: {:?}", path.parent());
// Build paths dynamically with PathBuf
let mut config_path = PathBuf::from("/home/user");
config_path.push(".config");
config_path.push("app");
config_path.push("settings.toml");
println!("Config: {}", config_path.display());
// /home/user/.config/app/settings.toml
}You can also create directories and check if paths exist:
use std::fs;
fn main() -> std::io::Result<()> {
// Create a single directory
fs::create_dir("output")?;
// Create nested directories (like mkdir -p)
fs::create_dir_all("data/raw/2024")?;
// List directory contents
for entry in fs::read_dir("src")? {
let entry = entry?;
println!("{}", entry.path().display());
}
// Remove a file
fs::remove_file("temp.txt")?;
// Remove a directory (must be empty)
fs::remove_dir("output")?;
Ok(())
}Error handling for I/O
All I/O operations return io::Result<T>, which is an alias for Result<T, io::Error>. You can match on the error kind for fine-grained handling:
use std::fs;
use std::io::ErrorKind;
fn main() {
match fs::read_to_string("config.toml") {
Ok(content) => println!("Config: {}", content),
Err(e) => match e.kind() {
ErrorKind::NotFound => {
println!("Config file not found, using defaults");
}
ErrorKind::PermissionDenied => {
println!("Permission denied! Check file permissions.");
}
_ => {
println!("Unexpected error: {}", e);
}
},
}
}Summary
use std::fs;
use std::io::Write;
use std::path::Path;
// Reading
fs::read_to_string("file.txt")? // Read whole file
BufReader::new(file).lines() // Read line by line
// Writing
fs::write("file.txt", content)? // Write whole file
File::create("file.txt")? // Create/overwrite
writeln!(file, "{}", data)? // Write formatted
// Appending
OpenOptions::new().append(true).open("f")?
// Paths
Path::new("dir/file.rs") // Create a path
PathBuf::from("/home").push("dir") // Build a path
path.exists() / is_file() / extension()Your turn
Try running the file I/O demo with cargo run and verify with cargo check: