Rust Paths and Text Across Platforms
Path and text rules change from one system to another. A backslash is a separator on Windows and a plain character here.
The separator is a value
std::path::Path is Rust’s type for a file path. std::path::MAIN_SEPARATOR is the character this system puts between path pieces. Here it is a slash:
println!("separator = {}", std::path::MAIN_SEPARATOR);A backslash is just a letter here
On unix machines a backslash is an ordinary filename character, not a separator. This path is one single piece here. On Windows it would be two:
let pieces = Path::new("a\\b").components().count();
println!("pieces = {pieces}");Windows text ends lines with two characters
A file saved on Windows ends each line with \r\n. lines() knows this, and quietly drops the \r:
let text = "one\r\ntwo\r\n";
let clean: Vec<&str> = text.lines().collect();
println!("lines = {:?}", clean);Split by hand and the \r stays
split('\n') is not so kind. Each piece keeps its \r, and a trailing empty piece appears. A comparison against "one" then fails in a confusing way:
let raw: Vec<&str> = "one\r\ntwo\r\n".split('\n').collect();
println!("split keeps = {:?}", raw);Case is part of the name — in Rust
Rust compares path text exactly, letter case included. Whether Notes.txt and notes.txt are the same file is the filing system's decision. Systems disagree — here they would be two files:
println!("equal as paths = {}", Path::new("Notes.txt") == Path::new("notes.txt"));Example
use std::path::Path;
fn main() {
println!("separator = {}", std::path::MAIN_SEPARATOR);
let pieces = Path::new("a\\b").components().count();
println!("pieces in a\\b = {pieces}");
let pieces2 = Path::new("a/b").components().count();
println!("pieces in a/b = {pieces2}");
let text = "one\r\ntwo\r\n";
let clean: Vec<&str> = text.lines().collect();
println!("lines = {:?}", clean);
let raw: Vec<&str> = text.split('\n').collect();
println!("split keeps = {:?}", raw);
}Key points
- Use components() to take a path apart: it understands this system's separators, and string splits do not
- Read text with lines() when the file may come from another system: it hides both line-ending styles
- Write text with \n and let println! and writeln! do their plain work: modern tools on every system read it without complaint
- Never build a path by gluing text with / or \ yourself.
- push and join, from the Paths lesson, already know this machine's rules
- Two paths can print alike and still differ — compare Path values, not printed text, when it matters.
In this example, every line is a small cross-platform lesson. The separator here is /. The path a\b counts as one piece. A backslash is a letter in a name on this machine. The same text on Windows would count as two pieces. The a/b path counts two pieces, as expected. Then the Windows-style text arrives. lines() hands back clean pieces. split('\n') leaves a \r riding on each one, plus an empty piece at the end. Same text, two readings — the tool decides.
Note
display() prints a path for people.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
A Linux program reads a Windows-made text file and splits it with split('\n'). What goes wrong?