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(), havePathimplfmt::Display. This makesPatheasier to use for new Rust developers, but it still hides the lossy-ness/cost of the UTF-8 conversion. It also makesPathinconsistent withOsStrwhich 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.