File System Operations
Path and PathBuf
std::path::Path (an unsized borrowed path slice, analogous to str) and std::path::PathBuf (an owned, growable heap-allocated path, analogous to String) are the standard types for representing filesystem paths in Rust.
Why Paths Use OsStr / OsString Instead of str / String
On real operating systems, not every valid filesystem path is valid UTF-8:
- On Unix (Linux, FreeBSD), filenames are arbitrary sequences of non-null bytes (
[u8]without0x00), which may contain non-UTF-8 byte sequences. - On Windows, filenames are sequences of potentially unpaired 16-bit UTF-16 code units (
[u16]/ WTF-8).
Therefore, Path is a #[repr(transparent)] wrapper around std::ffi::OsStr, and PathBuf wraps std::ffi::OsString, guaranteeing that Rust programs can open, copy, and traverse every file on disk without failing on non-UTF-8 filenames!
String vs. OS-String vs. Path Type Matrix:
Borrowed Slice (`!Sized`) Owned Heap Buffer (`Sized`)
------------------------- ---------------------------
UTF-8 Text: `str` (&str) <-----> `String`
| |
v (Infallible `AsRef`) v
OS String: `OsStr` (&OsStr) <-----> `OsString`
| |
v (`#[repr(transparent)]`) v
Filesystem: `Path` (&Path) <-----> `PathBuf` (`Deref<Target = Path>`)
Converting Between Path and Strings (to_str vs. to_string_lossy vs. display)
Because a Path might contain non-UTF-8 bytes, there is no infallible .as_str() -> &str method on Path. Instead, Path provides three conversion methods depending on your needs:
Method on &Path |
Return Type | When to Use |
|---|---|---|
.display() |
std::path::Display<'_> |
Formatting paths in user/log messages (println!("opened {}", path.display())) — zero allocation! |
.to_str() |
Option<&str> |
Zero-copy conversion when your application strictly requires valid UTF-8 (None if path contains non-UTF-8 bytes) |
.to_string_lossy() |
Cow<'_, str> |
Returns Cow::Borrowed(&str) (zero allocation!) if valid UTF-8, or Cow::Owned(String) with replacement characters `` if non-UTF-8 |
.as_os_str() |
&OsStr |
Passing paths directly to std::process::Command or OS APIs without losing non-UTF-8 bytes |
use std::path::{Path, PathBuf};
fn inspect_path(path: &Path) {
println!("display(): {}", path.display());
println!("to_str(): {:?}", path.to_str());
println!("is_absolute(): {}", path.is_absolute());
}
fn main() {
let borrowed: &Path = Path::new("/var/log/syslog");
let mut owned: PathBuf = PathBuf::from("/var/log");
owned.push("nginx/access.log");
// `PathBuf` deref-coerces to `&Path` automatically:
inspect_path(borrowed);
inspect_path(&owned);
}
display(): /var/log/syslog
to_str(): Some("/var/log/syslog")
is_absolute(): true
display(): /var/log/nginx/access.log
to_str(): Some("/var/log/nginx/access.log")
is_absolute(): true