Path and PathBuf
Functional Programming
Tutorial
The Problem
File paths are not plain strings: / vs. \ as separator, drive letters on Windows, UNC paths, relative vs. absolute, symlink resolution. Treating paths as String leads to bugs: manual format!("{}/{}", dir, file) fails on Windows, double-slash collisions, and no semantic operations like parent() or extension(). Rust provides Path (borrowed, like &str) and PathBuf (owned, like String) as first-class types that model the OS path semantics correctly on every platform.
🎯 Learning Outcomes
.join() chaining.extension() returning Option<&OsStr>.file_stem().parent() returning Option<&Path>Path/PathBuf and &str/String with .to_str() and .to_path_buf()Code Example
#![allow(clippy::all)]
// 491. Path and PathBuf handling
use std::path::{Path, PathBuf};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_join() {
let p = PathBuf::from("/a").join("b").join("c");
assert_eq!(p.to_str().unwrap(), "/a/b/c");
}
#[test]
fn test_ext() {
assert_eq!(Path::new("f.txt").extension().unwrap(), "txt");
}
#[test]
fn test_stem() {
assert_eq!(Path::new("f.txt").file_stem().unwrap(), "f");
}
#[test]
fn test_parent() {
assert_eq!(Path::new("/a/b/c").parent().unwrap(), Path::new("/a/b"));
}
}Key Differences
Path/PathBuf are distinct types from str/String; OCaml's Filename functions accept and return plain string..join() uses the OS separator automatically; OCaml's Filename.concat also handles this, but ^ concatenation does not.OsStr return types**: Rust's extension()/file_stem() return Option<&OsStr>, handling non-UTF-8 filenames; OCaml returns string (bytes, no encoding guarantee).Deref coercion**: PathBuf derefs to Path, enabling code that accepts &Path to work with PathBuf references; OCaml has no such coercion.OCaml Approach
OCaml's standard library operates on plain strings for file paths. The Filename module provides portable operations:
Filename.concat "/a" "b" (* "/a/b" *)
Filename.extension "f.txt" (* ".txt" — includes the dot *)
Filename.chop_extension "f.txt" (* "f" *)
Filename.dirname "/a/b/c" (* "/a/b" *)
Filename.basename "/a/b/c" (* "c" *)
OCaml's Fpath library (third-party) provides a type-safe path abstraction similar to Rust's Path/PathBuf.
Full Source
#![allow(clippy::all)]
// 491. Path and PathBuf handling
use std::path::{Path, PathBuf};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_join() {
let p = PathBuf::from("/a").join("b").join("c");
assert_eq!(p.to_str().unwrap(), "/a/b/c");
}
#[test]
fn test_ext() {
assert_eq!(Path::new("f.txt").extension().unwrap(), "txt");
}
#[test]
fn test_stem() {
assert_eq!(Path::new("f.txt").file_stem().unwrap(), "f");
}
#[test]
fn test_parent() {
assert_eq!(Path::new("/a/b/c").parent().unwrap(), Path::new("/a/b"));
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_join() {
let p = PathBuf::from("/a").join("b").join("c");
assert_eq!(p.to_str().unwrap(), "/a/b/c");
}
#[test]
fn test_ext() {
assert_eq!(Path::new("f.txt").extension().unwrap(), "txt");
}
#[test]
fn test_stem() {
assert_eq!(Path::new("f.txt").file_stem().unwrap(), "f");
}
#[test]
fn test_parent() {
assert_eq!(Path::new("/a/b/c").parent().unwrap(), Path::new("/a/b"));
}
}
Exercises
fn list_rust_files(dir: &Path) -> Vec<PathBuf> using std::fs::read_dir that returns all .rs files recursively.normalize(p: &Path) -> PathBuf that resolves .. components without hitting the filesystem (lexical normalisation only)..join() and verify it serialises correctly on both Unix and Windows using cfg!(target_os = "windows").