Paths
Recipe | Crates | Categories |
---|---|---|
Canonicalize a Path | ||
Ensure your Paths are UTF-8 with camino | ||
Create and Manipulate Paths |
Create and Manipulate Paths
std::path
↗ provides types and functions for working with file paths. Its two main types are Path
and PathBuf
. The relationship between them is directly analogous to str
and String
:
Path
↗ is an unsized, immutable sequence of characters representing a file path. Because it is unsized, you almost always see it used as a borrowed slice:&Path
. It is a "view" of a path string. It's efficient because it doesn't require a memory allocation.PathBuf
↗ is an owned, mutable, growable buffer containing a file path. UsePathBuf
when you need to build or modify a path.
Note that Path
and PathBuf
are thin wrappers around OsString
and OsStr
respectively, meaning that they work directly on strings according to the local platform's path syntax. Path methods that do not access the filesystem, such as Path::starts_with
and Path::ends_with
, are case sensitive no matter the platform or filesystem. An exception to this is made for Windows drive letters.
The following example demonstrates the use of Path
and PathBuf
:
use std::path::Path; use std::path::PathBuf; /// Displays the path and extract file names, extensions, and other path /// components from a path. /// /// Note that many path-handling functions use `AsRef<Path>` as their input /// type, and therefore generically accept `&Path`, `&PathBuf`, `&str`, /// `&OsStr`... fn inspect_path<P: AsRef<Path>>(path: P) { let p: &Path = path.as_ref(); // The `display()` method safely prints paths that may contain non-Unicode // data. This may perform lossy conversion, depending on the platform. println!("\nInspecting: {}", p.display()); // `parent` returns the parent directory as an `Option<&Path>`: if let Some(parent) = p.parent() { println!(" Parent: {}", parent.display()); } // Returns the last component of the path (e.g., `file.txt`) as an // `Option<&OsStr>`. `file_name` is a misnomer for a path to a directory: if let Some(filename) = p.file_name() { println!(" Last Component: {filename:?}"); } // Returns the file extension (without the leading dot) as an // `Option<&OsStr>`: if let Some(ext) = p.extension() { println!(" Extension: {ext:?}"); } print!(" All Components:\n "); for component in p.components() { print!(" {component:?}"); } println!(); } fn main() { // Create a `Path` from a string literal: let _path = Path::new("./temp/documents/report.txt"); // Create an owned `PathBuf` to build a new path: let mut path_buffer = PathBuf::from("./temp"); // Manipulate the `PathBuf`: path_buffer.push("downloads"); // Append a directory. path_buffer.push("archive.zip"); // Append a filename. println!("Built path: {}", path_buffer.display()); // You can change the extension: path_buffer.set_extension("tar.gz"); println!("Changed extension: {}", path_buffer.display()); // You can remove the last component: path_buffer.pop(); // Removes "archive.tar.gz" println!("Popped component: {}", path_buffer.display()); inspect_path(&path_buffer); // You can also use `collect` to build a `PathBuf` from its parts: let path_buffer: PathBuf = ["/", "home", "user"].iter().collect(); println!("Another path: {}", path_buffer.display()); }
Canonicalize a Path
The following returns the canonical, absolute form of a path with all intermediate components normalized and symbolic links resolved:
use std::fs; use std::path::PathBuf; fn main() -> std::io::Result<()> { // Create a directory for this example. let path = "./temp/examples"; fs::create_dir_all(path)?; // Canonicalize a path. // The path must exist. let normalized_path: PathBuf = fs::canonicalize("./temp/examples/../examples")?; println!("Canonical path: {}", normalized_path.display()); // You may also use `Path::canonicalize`: // let path = std::path::Path::new("/tmp/xmpl/../xmpl/bar.rs"); // assert_eq!(path.canonicalize().unwrap(), // PathBuf::from("/tmp/xmpl/bar.rs")); Ok(()) }
Ensure your Paths are UTF-8
The camino
crate provides an alternative to the standard Path
and PathBuf
types that are guaranteed to be UTF-8 encoded. While Rust strings are UTF-8, file paths on many operating systems are not, which can lead to complex error handling. camino
ensures your path types are always valid UTF-8, just like your strings:
use std::path::PathBuf; use camino::Utf8PathBuf; fn main() { // The standard `PathBuf` can contain non-UTF-8 data: let mut path = PathBuf::from("/home/user"); path.push("résumé.pdf"); // This might be non-UTF-8 on some systems. // .to_str() returns an Option, which can be `None`. match path.to_str() { Some(s) => println!("The path is: {s}"), None => println!("Path is not valid UTF-8."), } // `Utf8Path` and `Utf8PathBuf` work just like their standard library // counterparts but guarantee their content is valid UTF-8, eliminating // the need to check. // Create a new Utf8PathBuf. The `from` method works like the standard one. let mut path = Utf8PathBuf::from("data/files/résumé"); println!("Initial path: {path}"); // Push a new component onto the path. // This is guaranteed to be UTF-8. path.push("résumé-2025.txt"); println!("Final path: {path}"); // You can directly use the path as a `&str` without any `to_str()` call. if path.as_str().ends_with(".txt") { println!("This is a text file."); } // You can also get e.g. the file name directly: if let Some(file_name) = path.file_name() { println!("File name is: {file_name}"); } }
References
Related Topics
- Directories.
- Directory Traversal.
- Reading and Writing Files.
- Symbolic Links.