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

112 views Asked by At

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!

2

There are 2 answers

10
user4815162342 On BEST ANSWER

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.

1
Colonel Thirty Two On

AsRef is a trait meant for types that encapsulate a reference to an underlying object. For example, AsRef<Path> is implemented by &Path, PathBuf, Rc<Path>, Arc<Path>, and others. It's convenient when you don't care whether a type is borrowed or owned.

However, AsRef<T> is not necessarily cloneable: &mut T isn't, for example.

You can require that your P implements Clone for your structure to implement Clone, which will solve your immediate compile error:

impl <P: AsRef<Path> + std::fmt::Debug + Clone> Clone for MyConfig<P>

But keep in mind that if P is a reference, its clone will be as well. Clones of MyConfig will just hold copies of the reference without ownership. This may or may not be what you want. Since clone() requires that you return Self - the structure with the same generic parameters as the one being cloned, you can't get around that with the standard Clone trait, and will have to implement such "deep copying" with either a different method or trait.