#267 Jul 18, 2026

267. Path::file_name — rsplit('/') Hands You Empty Strings and '..'

Grabbing the last path segment with rsplit('/') returns "" for logs/ and ".." for logs/.. — two values that were never file names. Path::file_name reasons in components and returns None instead.

The string version looks harmless:

1
2
3
4
5
6
7
8
let name = "logs/app.log".rsplit('/').next();
assert_eq!(name, Some("app.log")); // so far so good

let name = "logs/".rsplit('/').next();
assert_eq!(name, Some("")); // empty "file name"

let name = "logs/..".rsplit('/').next();
assert_eq!(name, Some("..")); // that's a traversal, not a file

Feed either of those into a join or a delete-by-name and you have a bug — or worse. Path::file_name works on components, not characters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::ffi::OsStr;
use std::path::Path;

let p = Path::new("logs/app.log");
assert_eq!(p.file_name(), Some(OsStr::new("app.log")));

// Trailing slash? Still the same last component
let dir = Path::new("logs/");
assert_eq!(dir.file_name(), Some(OsStr::new("logs")));

// '..' is not a name — you get None, not a footgun
assert_eq!(Path::new("logs/..").file_name(), None);
assert_eq!(Path::new("/").file_name(), None);

The rules it applies:

  • the last component wins, so a trailing separator changes nothing: logs/logs
  • a path ending in .. has no final name to give you → None
  • the root itself has no name → None

Like Path::extension from bite 266, the return type is Option<&OsStr>: the “there is no file name here” case arrives as a None the compiler makes you handle, instead of an empty string or a .. sneaking into your filesystem calls.

← Previous 266. Path::extension — Splitting on '.' Breaks on .gitignore Next → 268. fs::read_dir — The Filesystem Doesn't Sort for You