Summary: Deprecate Path::display()
in favor of using Path::to_str()
and Path::to_string_lossy()
when one wants to print a path using fmt::Display
.
If you want to print a path using println!()
, trying to print the path directly:
println!("{}", my_path);
won’t work. Instead one must use the Path::display()
method to get a display-able version of the path:
println!("{}", my_path.display());
This is a minor difference, but it’s inconsistent with conventions in the standard library and winds up being a pain point for new users (at least it was for me).
According Huon Wilson, Path
doesn’t impl fmt::Display
directly because the conversion is lossy. In theory forcing the user to call Path::display()
helps to make this cost clearer, but I’d argue that it fails at this purpose. Worse yet, Path::to_string_lossy()
already does the exact same thing in a way that makes both the purpose and the costs clearer.
I think the right solution is to get rid of Path::display()
. It’s functionally equivalent to Path::to_string_lossy()
, but fails to obviate the associated costs of the UTF-8 conversion the way that to_string_lossy()
does. Additionally, if the user wants to guarantee they only print UTF-8 paths, then they can use to_str()
to perform a cheap, lossless conversion and handle any failures as they see fit. This addresses all concerns except for making Path
easy to use for new developers; You still can’t print a Path
directly. The situation is at least improved, though, because it’s clearer what to_string_lossy()
is doing to create a display-able path.
Impact
While we can’t completely remove Path::display()
, deprecating it is likely to introduce warnings into a large amount of existing code. While I don’t have any numbers, it’s bound to have a pretty widespread impact. Fortunately, most usage of Path::display()
should still work as intended if replaced directly with Path::to_string_lossy()
. The only case that would break is if someone were referring to path::Display
by name directly, e.g. if they were storing it in a struct. I can’t imagine that’s a common use-case, though, as there’s no advantage to holding onto a path::Display
object.
Interestingly, this change does have the potential to improve performance in a select few use-cases. Consider the following:
let display = my_path.display();
for _ in 0..100 {
println!("My path is: {}", display);
}
Each time the path is printed it will perform the UTF-8 conversion, which will allocate if my_path
contained non-utf8 characters. If we replace my_path.display()
with my_path.to_string_lossy()
, the code still compiles but only performs the allocation and UTF-8 conversion once.
Alternatives
- Do nothing. Valid, doesn’t break any code, but leaves this pain point unaddressed.
- Remove
Path::display()
, havePath
implfmt::Display
. This makesPath
easier to use for new Rust developers, but it still hides the lossy-ness/cost of the UTF-8 conversion. It also makesPath
inconsistent withOsStr
which doesn’t implfmt::Display
.
Side Note: Comparison to OsStr
Functionally, Path
is just a wrapper around OsStr
that adds utility methods like exists()
and is_absolute()
. As such, I’d expect that printing a Path
and printing an OsStr
should be the same, but OsStr
has no display()
method, it only has to_str()
and to_string_lossy()
. Deprecating Path::display()
brings Path
in line with OsStr
, which I think is an improvement.