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
101: Directory Traversal with read_dir
When you first start poking around the std::fs module, read_dir seems straightforward enough. You give it a path, it gives you an iterator. But there's a specific friction point in Rust's API here that trips up almost everyone: the double Result. You aren't just dealing with the possibility that the directory doesn't exist; you're dealing with the possibility that individual entries inside that directory might be unreadable due to permission changes or disk glitches while you're iterating.
The instinct to unwrap everything
If we're building a simple tool—say, a utility that scans a folder for .log files to aggregate them—your first instinct might be to just loop through and unwrap. It feels cleaner in the code, and you might think, "I know these files are there, why bother with the overhead?"
use std::fs;
fn get_logs_naive(path: &str) {
let entries = fs::read_dir(path).unwrap();
for entry in entries {
// This is where the danger hides
let entry = entry.unwrap();
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("log") {
println!("Found log: {:?}", path);
}
}
}
This works fine in a controlled environment. But in production, this is a landmine. If the OS encounters a single file it can't access—maybe a system file that got dropped in there or a locked file—the whole program panics and dies. I've seen entire data pipelines crash because one corrupted inode in a directory of ten thousand files triggered an unwrap(). You've essentially tied the stability of your entire application to the perfect health of the filesystem.
Handling the noise gracefully
The professional way to handle this is to acknowledge that directory traversal is inherently "noisy." You don't want one bad entry to kill the process; you just want to skip it or log a warning. I usually lean toward filter_map here. It allows us to flatten that nested Result and discard the errors in one fluid motion.
use std::fs;
use std::path::PathBuf;
fn get_logs_robust(path: &str) -> Vec {
fs::read_dir(path)
.expect("Failed to open directory") // The directory itself MUST exist
.filter_map(|entry| entry.ok()) // Silently skip entries we can't read
.map(|entry| entry.path())
.filter(|path| {
path.extension()
.and_then(|s| s.to_str()) == Some("log")
})
.collect()
}
Notice the trade-off here. We are still using expect on the initial read_dir call because if the root directory is missing, the function can't do its job. However, inside the loop, we use entry.ok(). This converts the Result<DirEntry, Error> into an Option<DirEntry>. When filter_map sees a None, it simply ignores it and moves to the next file.
Why this matters for performance
You might wonder if this functional approach is slower than a for loop. In Rust, generally, no. The compiler is incredibly good at optimizing these iterator chains into the same machine code as a loop. The real win is in the mental model: you're explicitly defining what constitutes a "valid" entry. You've separated the critical failure (directory missing) from the expected noise (unreadable files). I've found that this pattern prevents a huge category of "it worked on my machine" bugs that only appear when the code hits a real-world server with restrictive permissions.
📋 Practical Task
Build a Config File Finder
Write a function called find_config_files that takes a directory path as a string slice. The function should scan the directory and return a Vec<PathBuf> containing only the files that end with the .toml or .conf extensions.
Your implementation must meet these criteria:
- It must not panic if it encounters a file it doesn't have permission to read (it should simply skip it).
- It should handle the initial directory opening error by returning an empty
Vecrather than panicking. - It must correctly filter for both extensions using a case-sensitive check.
There are no comments for now.