🎓 A1Learner
🔍
0 of 149 lessons completed

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?