Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
59: Working with Files and Paths
I've spent way too many hours of my career debugging "File Not Found" errors that only happened on a teammate's machine. Usually, it's because someone hardcoded a path using forward slashes on Windows, or assumed the working directory was the project root. Let's dive into how Rust handles this so you don't have to make those same mistakes.
Imagine we're building a simple save-game system. We need to find a folder called saves and read a file named player_stats.txt.
Wait, where is my file?
My first instinct is usually to just pass a string to File::open. It feels fast. Let's try that:
use std::fs::File;
use std::io::Read;
fn main() -> std::io::Result<()> {
// I'm assuming the file is right here in the project root
let mut file = File::open("saves/player_stats.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
println!("Stats: {}", contents);
Ok(())
}
This works fine on my macOS machine. But here's the problem: "saves/player_stats.txt" is just a string. If I ship this to a user on Windows, that forward slash might cause issues depending on the environment, and more importantly, it's completely rigid. What if the saves are actually stored in the user's home directory or a specific app-data folder? We need something more robust than a &str.
The String Path Trap
Rust gives us Path and PathBuf for this. I like to think of Path as a slice (like &str) and PathBuf as an owned, mutable string (like String). If you're building a path dynamically, you want PathBuf.
Let's rewrite this to be more flexible. I'll start with a base directory and append the filename to it. If I try to just use string concatenation, I'm back to the slash problem:
use std::path::PathBuf;
let mut path = PathBuf::from("saves");
path.push("player_stats.txt");
// Now 'path' is saves/player_stats.txt (or saves\player_stats.txt on Windows)
The .push() method is the secret sauce here. It doesn't just tack on characters; it understands the operating system's path separators. It handles the "glue" between folders for us. I've found that whenever I'm tempted to use format!("{}/{}", dir, file), I need to stop myself and use PathBuf instead.
Building paths without the headache
Now, let's get realistic. Your app shouldn't just guess where the files are. What if the folder doesn't even exist yet? If I try to open a file in a directory that isn't there, Rust will throw an io::Error. Let's add a check and create the directory if it's missing.
use std::fs;
use std::path::Path;
fn ensure_save_dir(dir: &Path) -> std::io::Result<()> {
if !dir.exists() {
println!("Directory missing, creating it...");
fs::create_dir_all(dir)?;
}
Ok(())
}
Notice I used &Path in the argument. This is a pro tip: if your function only needs to read the path, take a reference to Path. This allows the caller to pass in either a &PathBuf or a &str, because Path implements AsRef<Path>. It makes your API much friendlier.
Actually getting the data out
Finally, let's put it all together. I want to read the file, but I don't want the program to crash if the file is empty or missing—I want it to return a default set of stats. This is where fs::read_to_string comes in handy; it's a convenient wrapper that handles opening and reading in one go, so we don't have to manually manage the File handle.
use std::fs;
use std::path::PathBuf;
fn load_game_stats() -> String {
let mut path = PathBuf::from("saves");
path.push("player_stats.txt");
// We use match here because we want a default value instead of crashing
match fs::read_to_string(&path) {
Ok(content) => content,
Err(_) => {
println!("No save found, using defaults.");
"Health: 100, Level: 1".to_string()
}
}
}
fn main() {
let stats = load_game_stats();
println!("Current Stats: {}", stats);
}
One last thing: if you're dealing with massive files, read_to_string is a trap because it loads the entire file into memory. For a small save file, it's perfect. For a 2GB log file, you'd want to use std::io::BufReader to read it line-by-line. But for our purposes, this is the cleanest way to get the job done.
📋 Practical Task
Exercise: The Project Log Archiver
Your goal is to create a tool that organizes logs. Write a program that does the following:
- Defines a base directory named
logsand a filename namedsession.log. - Uses
PathBufto construct the full path to that file. - Checks if the
logsdirectory exists; if not, it creates it. - Checks if
session.logexists. If it does, it reads the content and prints "Existing log found: [content]". - If
session.logdoes not exist, it creates the file and writes "Session started" into it usingstd::fs::write.
Requirement: Ensure you use &Path for any helper functions to keep the code flexible!
There are no comments for now.