std::fmt::Display trait bound for std::path::Path

Hello,

I'm using a trait bounded generic

P: AsRef<Path>

for my struct. It seems to be working well. I'm attempting to get the string version of the path, but am struggling. std::path::Path has a display method that returns a std::path::Display, but it seems I need a trait bound for std::fmt::Display.

Here is a minimally reproducible code example:

use std::path::Path;
use std::fmt::Display;

struct Foo<P>
where
    P: AsRef<Path>,
{
    path: P,
}

impl<P> Foo<P>
where
    P: AsRef<Path> + Display,
{
    fn new(
        path: P,
    ) -> Self {
        let s = format!(
            "{}",
            path,
        );

        Self {
            path,
        }
    }   
}

fn main() {
    let foo = Foo::new(
        Path::new("/etc/passwd"),
    );  
}

...and here is the compiler error:

   Compiling path_test v0.1.0 (path_test)
error[E0277]: `Path` doesn't implement `std::fmt::Display`
  --> src/main.rs:31:9
   |
30 |     let foo = Foo::new(
   |               -------- required by a bound introduced by this call
31 |         Path::new("/etc/passwd"),
   |         ^^^^^^^^^^^^^^^^^^^^^^^^ `Path` cannot be formatted with the default formatter; call `.display()` on it
   |
   = help: the trait `std::fmt::Display` is not implemented for `Path`
   = note: call `.display()` or `.to_string_lossy()` to safely print paths, as they may contain non-Unicode data
   = note: required because of the requirements on the impl of `std::fmt::Display` for `&Path`
note: required by a bound in `Foo::<P>::new`
  --> src/main.rs:13:22
   |
13 |     P: AsRef<Path> + Display,
   |                      ^^^^^^^ required by this bound in `Foo::<P>::new`
14 | {
15 |     fn new(
   |        --- required by a bound in this

For more information about this error, try `rustc --explain E0277`.
error: could not compile `path_test` due to previous error

Is there any way to add a std::fmt::Display trait to std::path::Path?

Or is there a better way of structuring my code, and retaining the AsRef bound?

Thanks for any pointers!

-m

  • Use self.path.as_ref() to get a &Path
    • Utilizing your P: AsRef<Path> bound
  • Then call .display().to_string() on the &Path
    • Or format!("{}", path.display()) if you prefer

Playground.

1 Like

As you can see, Path doesn't impl Display. Why? Because most operating systems allows to have file system paths which is not a valid unicode string. But the Display can handle only valid unicode strings. The .display() method returns some newtype which performs lossy conversion on print, which means if the original path contains invalid unicode text, result of the .to_string() contains different content since the String can only holds valid unicode text. It's perfectly ok for human readable outputs like logging, but it may not be a good idea to hold it as file system path.

1 Like

You should read the compiler error message. It tells you the exact solution:

1 Like

Hi @quinedot,

Thanks for the help! The .as_ref() was exactly what I needed.

I'm a little murky on when I need to call that method. From the docs of AsRef:

AsRef auto-dereferences if the inner type is a reference or a mutable reference (e.g.: foo.as_ref() will work the same if foo has type &mut Foo or &&mut Foo)

it looks like it should have auto-deref'ed for my reference.

For example, the following compiles, as expected:

use std::path::Path;

struct Foo<P>
where
    P: AsRef<Path>,
{
    _path: P,
}

impl<P> Foo<P>
where
    P: AsRef<Path>,
{
    fn new(
        _path: P,
    ) -> Self {
        let _s = format!(
            "{}",
            _path.as_ref().display(),
        );

        Self {
            _path,
        }
    }
}

fn main() {
    let _path = Path::new("/etc/passwd");
    let _foo = Foo::new(
        &_path,
    );

    let _bar = Foo::new(
        "/etc/passwd",
    );
}

but if I remove the .as_ref(), it doesn't. Shouldn't rust auto-deref the reference?

Thanks for any insight!

-m

Within the implementation block (e.g. within the new function definition), the only thing you know about P is that it implements AsRef<Path>. So the only thing you can do with it (other than moving it around) is to call .as_ref() on it.

The documentation about auto-deref is trying to convey that you can also call .as_ref() -- the only AsRef method -- on a

&P
&&P
&&&&&&&&&P
&mut P
&mut &mut P
&mut &&& &mut &mut &&mut P

Et cetera. This is due to a couple blanket trait implementations, not a special-case of AsRef in the language. It does not mean you can call Path methods on P: AsRef<P> -- you have to explicitly call as_ref() to get that conversion.

That documentation could be improved to clarify this.

3 Likes

Good to know. Thank you for taking the time to further explain it.

I guess it really should. Did you open a PR?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.