Search code examples
filerustdirectorybuffer

How to implement Display trait for PathBuf


I was working with a library's function which accepts any values with a Display trait this is a problem because PathBuf wasn't implemented with Display trait.

This is my code:

//library: inquire
fn recurse_file(path: impl AsRef<Path>) -> std::io::Result<Vec<PathBuf>> {
    let mut buf = vec![];

    let entries = read_dir(path)?;

    for entry in entries {
        let entry = entry?;
        buf.push(entry.path())
    }
    Ok(buf)
}

pub fn read_arg() -> String {
    let option = recurse_file("./");

    let answer: Result<String, InquireError> = Select::new("Select a folder or file", option).prompt();

    match answer {
        Ok(dir) => dir,
        Err(_) => {
            exit(1);
        }
    }
}

The compiler suggests using {:?} or .display() and to_string_lossy() which both didn't work. I tried to implement Display for PathBuf but the compiler said that PathBuf is not defined in the current crate

impl Display for PathBuf {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "{}", self.0)
    }
}

EDIT: I tried to use struct to wrap Vec so I could implement Display for Vec. Interestingly, this method worked but return more errors at read_arg but I think this answered my question so I can close it.

code:

struct CustomPathBuf {
    path: Vec<PathBuf>
}

impl Display for CustomPathBuf {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "{:?}", self.path)
    }
}

Solution

  • Short Answer

    As the comments have pointed out, if you want to format a Path, you generally want to use Path::display. This also applies to PathBuf as it can be implicitly converted to &Path via Deref.

    let path = PathBuf::from("foo/bar.txt");
    
    println!("{}", path.display());
    

    General Answer

    When you want to implement Debug or Display on a type defined by different module, the general recommended approach is to create a wrapper type.

    For example, lets say I wanted to implement Display for Vec<PathBuf>. I could do this by creating a wrapper like this.

    // I use Vec<PathBuf> here to make this a bit easier to read/understand,
    // but it is generally better to use a slice instead.
    pub struct NumberedPathList<'a>(pub &'a Vec<PathBuf>);
    
    impl Display for NumberedPathList<'_> {
        fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
            for (index, path) in self.0.iter().enumerate() {
                writeln!(f, "{}: {}", index, path.display())?;
            }
            Ok(())
        }
    }
    

    To make it easier to use our wrapper type, I recommend also creating a trait to allow for easy conversion.

    pub trait ToNumberedList {
        fn display_numbered(&self) -> NumberedPathList<'_>;
    }
    
    impl ToNumberedList for Vec<PathBuf> {
        fn display_numbered(&self) -> NumberedPathList<'_> {
            NumberedPathList(self)
        }
    }
    
    // Somewhere else in your code
    let foo = vec![
        PathBuf::from("/a/b/c/d.txt"),
        PathBuf::from("/a/b/c/f.rs"),
        PathBuf::from("/a/b/g.txt"),
    ];
    
    println!("{}", foo.display_numbered());
    
    // Output:
    // 0: /a/b/c/d.txt
    // 1: /a/b/c/f.rs
    // 2: /a/b/g.txt
    

    Rust Playground