182. Path::with_extension — Swap a File Extension Without Slicing Strings
You have report.txt and want report.md. Reaching for replace(".txt", ".md") or a rfind('.')? Stop — Path::with_extension returns a fresh PathBuf with the extension swapped, and it gets every edge case right.
The string-slicing trap
The naïve fix looks reasonable until you read it carefully:
| |
That second case is the bug: ./.bashrc has no extension — the leading dot is part of the name. Manual rfind('.') doesn’t know that.
The fix: Path::with_extension
| |
It returns a new PathBuf — original path untouched — and stays in OsStr land the whole way through, so non-UTF-8 paths survive intact.
Dotfiles are handled the way you’d want:
| |
No extension to start? It adds one instead of failing:
| |
Pass "" to strip the extension
The same method, with an empty string, removes the extension entirely — no separate without_extension API needed:
| |
Common pattern in build scripts: derive an output path from an input path.
| |
Only the last extension changes
with_extension replaces from the last dot — same rule as file_stem. For archive.tar.gz, that means only .gz gets swapped:
| |
That’s almost always what you want for compression tools. If you need to strip the whole .tar.gz and start over, call with_extension("") twice — or reach for file_prefix (see bite 116).
set_extension if you already own the PathBuf
The mutating sibling lives on PathBuf and avoids the allocation when you already own the path:
| |
Returns bool — true if the extension was set, false if the path had no file name to attach one to. Most callers ignore it.
Reach for with_extension (or set_extension) any time you’d otherwise write a rfind('.') or a replace(".old", ".new"). It’s been stable since Rust 1.0 — there’s no excuse left.