rust 45 lines · 8 steps

Normalizing filesystem paths in Rust

A stack-based walk collapses '.' and '..' segments into a clean canonical path, then reuses that logic to join two paths.

Explained by highlit
1pub fn normalize_path(input: &str) -> String {
2 let is_absolute = input.starts_with('/');
3 let has_trailing_slash = input.len() > 1 && input.ends_with('/');
4 let mut stack: Vec<&str> = Vec::new();
5 
6 for segment in input.split('/') {
7 match segment {
8 "" | "." => continue,
9 ".." => {
10 if let Some(&last) = stack.last() {
11 if last != ".." {
12 stack.pop();
13 continue;
14 }
15 }
16 if !is_absolute {
17 stack.push("..");
18 }
19 }
20 other => stack.push(other),
21 }
22 }
23 
24 let mut result = String::new();
25 if is_absolute {
26 result.push('/');
27 }
28 result.push_str(&stack.join("/"));
29 
30 if result.is_empty() {
31 return ".".to_string();
32 }
33 if has_trailing_slash && !result.ends_with('/') {
34 result.push('/');
35 }
36 result
37}
38 
39pub fn join_paths(base: &str, relative: &str) -> String {
40 if relative.starts_with('/') {
41 return normalize_path(relative);
42 }
43 let trimmed = base.trim_end_matches('/');
44 normalize_path(&format!("{trimmed}/{relative}"))
45}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A stack is the natural structure for resolving '..' because each parent reference simply pops the most recent segment.
  2. 2Capturing structural flags (absoluteness, trailing slash) up front lets you rebuild them faithfully after the segments are collapsed.
  3. 3Building higher-level operations like join on top of one normalizer keeps edge-case handling in a single place.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Normalizing filesystem paths in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code