Why Working with Files in Rust is Not a Pain, but a Pleasure?
\n\nIf you're used to Python, where open() solves almost everything, Rust might seem like a strict teacher. But behind this strictness lies incredible power. Rust won't let you accidentally leave a file open, read bytes as a string, or forget to check if a directory exists. In this article, we'll explore working with files and the file system in Rust — from simple operations to advanced techniques. You'll learn how to read, write, create, and delete files, as well as how to efficiently manage paths.
Ready? Let's go!
\n\nBasics: Open, Read, Close
\n\nRust doesn't have a built-in open() function like Python. Instead, we use the powerful std::fs module. Let's start with the simplest — reading an entire file.
Reading an Entire File into a String
\n\nThe simplest way to read a file is to use std::fs::read_to_string. This is analogous to file.read() in Python, but with safety guarantees.
use std::fs;\n\nfn main() {\n let content = fs::read_to_string("hello.txt")\n .expect("Failed to read file");\n println!("File contents:\\n{}", content);\n}\n\nNotice the .expect(). Rust doesn't have exceptions — it has Result. If the file doesn't exist, the program will "panic" with a clear message. For production code, it's better to use match or unwrap_or_else, but for learning, expect is ideal.
Writing to a File
\n\nWriting is also intuitive:
\n\nuse std::fs;\n\nfn main() {\n let data = "Hello, Rust!\\nThis is a test write.";\n fs::write("output.txt", data)\n .expect("Failed to write file");\n println!("File successfully written!");\n}\n\nThe fs::write method creates the file if it doesn't exist and overwrites the contents if the file exists. To append data to the end of a file, use OpenOptions.
Working with Paths: Path and PathBuf
\n\nRust has two main types for working with paths: Path (borrowed, immutable) and PathBuf (owned, mutable). Understanding the difference between them is key to efficient file system operations.
Creating and Manipulating Paths
\n\nuse std::path::{Path, PathBuf};\n\nfn main() {\n // Create a path\n let path = Path::new("data/subdir/file.txt");\n \n // Get path components\n println!("Parent directory: {:?}", path.parent());\n println!("File name: {:?}", path.file_name());\n println!("Extension: {:?}", path.extension());\n \n // Build a new path\n let mut new_path = PathBuf::from("backup");\n new_path.push("data");\n new_path.push("file.txt");\n println!("New path: {}", new_path.display());\n}\n\nThe .display() method is mandatory if you want to print the path to the terminal. Rust doesn't allow simply inserting a path into a string — this is protection against encoding issues.
Reading and Writing: Advanced Level
\n\nWhen you need more control, use File and BufReader/BufWriter.
Reading a File Line by Line
\n\nFor large files, reading everything into memory is inefficient. Use buffered reading:
\n\nuse std::fs::File;\nuse std::io::{BufRead, BufReader};\n\nfn main() {\n let file = File::open("large_file.txt")\n .expect("File not found");\n let reader = BufReader::new(file);\n \n for (index, line) in reader.lines().enumerate() {\n let line = line.expect("Error reading line");\n println!("Line {}: {}", index + 1, line);\n }\n}\n\nThis approach allows you to process files of any size without loading them entirely into RAM.
\n\nAppending Data to the End of a File
\n\n