Rust Real Paths
A real path is the one path that names a file after links and .. pieces are resolved. fs::canonicalize finds the real file behind links and .. pieces.
Absolute, on paper
absolute() joins the current folder in front. It never checks the disk:
let abs = std::path::absolute("note.txt").unwrap();
println!("absolute = {}", abs.is_absolute());Dots stay dots
absolute() does not tidy the path. A .. piece survives the trip:
let abs = std::path::absolute("sub/../note.txt").unwrap();
println!("keeps dots = {}", abs.ends_with("sub/../note.txt"));Canonicalize finds the real file
canonicalize asks the disk. Two spellings of one file give one answer:
let real = fs::canonicalize("sub/../note.txt")?;
let plain = fs::canonicalize("note.txt")?;
println!("same real file = {}", real == plain);Canonicalize follows links
A link is transparent to canonicalize. The answer names the file behind it:
let real = fs::canonicalize("link.txt")?;
println!("behind the link = {}", real.file_name().unwrap().to_string_lossy());Make a path relative
strip_prefix cuts a known base off the front. It returns Err when the base is not there:
let full = Path::new("reports/2026/sales.txt");
println!("{}", full.strip_prefix("reports").unwrap().display());
println!("other base fails = {}", full.strip_prefix("other").is_err());Example
use std::fs;
use std::path::{Component, Path};
fn main() {
let root = Path::new("real_demo");
let _ = fs::remove_dir_all(root);
fs::create_dir_all(root.join("sub")).expect("could not make folders");
fs::write(root.join("note.txt"), "real words").expect("could not write the note");
std::os::unix::fs::symlink("../note.txt", root.join("sub/link.txt"))
.expect("could not make the link");
let messy = root.join("sub/../note.txt");
let real = fs::canonicalize(&messy).expect("could not canonicalize");
let via_link = fs::canonicalize(root.join("sub/link.txt")).expect("could not follow the link");
println!("messy lands on = {}", real.file_name().unwrap().to_string_lossy());
println!("link lands on = {}", via_link.file_name().unwrap().to_string_lossy());
println!("same file = {}", real == via_link);
let abs = std::path::absolute(&messy).expect("could not absolutize");
println!("absolute keeps .. = {}", abs.components().any(|c| c == Component::ParentDir));
println!("missing fails = {}", fs::canonicalize(root.join("ghost.txt")).is_err());
fs::remove_dir_all(root).expect("could not clean up");
}Key points
- Use std::path::absolute(path) to make a path absolute without touching the disk: the file does not need to exist
- Use fs::canonicalize(path) to get the one real path: it resolves links and .. pieces, and it fails when the file is missing
- absolute() works on paper only: it keeps .. pieces, and it never looks at links
- Use path.strip_prefix(base) to make a path relative to a known base.
- It fails when the path does not start with the base
- Rust has no built-in call that computes a relative path between two unrelated paths.
- Tools use the pathdiff crate for that job
- A real path names a file in exactly one way, with links and .. pieces resolved.
In this example, the program builds a small stage first. It makes a folder, a note file, and a link inside a subfolder. The link target ../note.txt is read from the link's own folder. So it points at the note. canonicalize then answers three questions. The messy path lands on note.txt. The link lands on the same file. Both answers are equal. absolute() takes the same messy path and keeps its .. piece. The last line shows the hard rule. A missing file makes canonicalize fail.
Note
On Windows, canonicalize returns a long \\?\-style path.
Most tools strip that prefix before they show the path to a user.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Which call fails when the file does not exist?