Search code examples
rusttrait-bounds

Implement the Clone trait for a struct that holds a generic ref to a path via AsRef(Path)


I have a struct that looks like this:

pub struct MyConfig<P>
{
    // Path to file
    pub file_path: P,
    // Other settings
    pub setting1: u8,
    // [...]
}

impl <P: AsRef<Path> + std::fmt::Debug> MyConfig<P>
{
    pub fn with_path(file_path:: P) -> Result<Self>
    {
        MyConfig
        {
            file_path,
            setting1: 1
        }
    }
}

I need a way to clone that config object. I can't just #[derive(Clone)], because of the P trait bound. I tried implementing Clone manually, but I can't find a way to make a generic copy/clone of the file_path property. For example, why does this not work:

impl <P: AsRef<Path> + std::fmt::Debug> Clone for MyConfig<P>
{
    fn clone(&self) -> Self {
        Self { file_path: Path::new(self.file_path.as_ref().file_name()),
               setting1: self.setting1.clone(),
            }
    }
}

I'm quite new to Rust, so maybe this is a really dumb question (or implementation), but I'm really at my wits' end with this - seemingly trivial - problem!


Solution

  • The point of AsRef<Path> is to make your public functions more usable, so you can invoke them with values of types &str, String, &Path, and PathBuf. But there is no reason to hold a value so generic. This applies doubly if you're a novice and unsure of what AsRef<Path> does to begin with - in that case, accept AsRef<Path> in the public API, and immediately convert it to PathBuf:

    #[derive(Debug, Clone)]
    pub struct MyConfig {
        // Path to file
        pub file_path: PathBuf,
        // Other settings
        pub setting1: u8,
        // [...]
    }
    
    impl MyConfig {
        pub fn with_path(file_path: impl AsRef<Path>) -> Self {
            MyConfig {
                file_path: file_path.as_ref().to_owned(),
                setting1: 1,
            }
        }
    }
    

    Playground

    This way you get a fully owned object that can derive Clone and Debug.

    Remark for advanced usage: When you unconditionally create PathBuf, you can alternatively accept file_path: impl Into<PathBuf>, and create the pathbuf with file_path.into(). This will accept a similar choice of types like AsRef<Path> does, but will avoid a reallocation if the caller passes an owned String or PathBuf. This technique is used e.g. by the fs_err crate to avoid unnecessary allocation.