Efficient From<> implementations without copy-paste?

Let's say I have this type:

pub struct Id(std::path::PathBuf);

It looks reasonable to construct it from:

  1. any type that implements AsRef<std::path::Path>. In this case we will have to copy the type.
  2. an owned type: std::path::PathBuf. Here we just need to move it (no need to clone).

Initially I wanted to use Cow<>, because it looks like exactly what I need, it implements all the logic: if it is a borrow -> clone, otherwise -> move:

    pub fn into_owned(self) -> <B as ToOwned>::Owned {
        match self {
            Borrowed(borrowed) => borrowed.to_owned(),
            Owned(owned) => owned,
        }
    }

But then I quickly realised it is not a trait, and I cannot write this:

impl From<Cow<...>> for Id

My next attempt was to this:

impl From<&std::path::Path> for Id {
   fn from(value: &std::path::Path) -> Self {
       Id(value.to_path_buf())
   }
}
impl From<std::path::PathBuf> for Id {
   fn from(value: std::path::PathBuf) -> Self {
       Id(value)
   }
}

But there are two issues:

  • copy-paste. I just need Cow-like thing that can decide for me what it holds (reference or value) and then it should be able to give me owned version in the most efficient way. The fact that I do not have this, made me write two implementations of From.
  • I am not sure, but I am not using AsRef<std::path::Path>, which means that it will not be able to take any type that implements AsRef<std::path::Path>.

This version did not work either:

impl<T: AsRef<std::path::Path>> From<T> for Id {
    fn from(value: T) -> Self {
        Id(value.as_ref().to_path_buf())
    }
}
impl From<std::path::PathBuf> for Id {
    fn from(value: std::path::PathBuf) -> Self {
        Id(value)
    }
}
error[E0119]: conflicting implementations of trait `From<PathBuf>` for type `Id`
  --> prod/quotalib/src/id.rs:48:1
   |
43 | impl<T: AsRef<std::path::Path>> From<T> for Id {
   | ---------------------------------------------- first implementation here
...
48 | impl From<std::path::PathBuf> for Id {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Id`

which of course makes sense...


So, how would you write efficient (== no unnecessary clones) From trait without copy-paste?


To sum up:

I am trying to find a way to implement a (1) single From<> trait (== no copy-paste), that allows me to (2) create Id from:

  • any type that implements AsRef<std::path::Path>
  • or an owned type: std::path::PathBuf

(3) without cloning where it is not necessary (== if from() was called with PathBuf, there is no need to clone it).

Is there a way to satisfy all 3 points?

It is indeed not a trait, but you can implement From<Cow<...>> for Id:

use std::borrow::Cow;

pub struct Id(std::path::PathBuf);

impl From<Cow<'_, std::path::Path>> for Id {
   fn from(value: Cow<'_, std::path::Path>) -> Self {
       Id(value.into_owned())
   }
}

Playground.

3 Likes

But then I cannot use it as I initially wanted:

playground

fn f() {
    let owned_path = std::path::PathBuf::from("/some/owned/path");
    let borrowed_path = std::path::Path::new("/some/borrowed/path");
    
    Id::from(owned_path); // <--- should compile
}
   |
15 |     Id::from(owned_path);
   |     ^^ the trait `From<PathBuf>` is not implemented for `Id`
   |

It means that everyone who creates Id, would have to create Cow first, which is not very convenient.

Just that implementation (i.e. removing the extra one for PathBuf) should be enough to get your interface to work:

pub struct Id(std::path::PathBuf);

impl<T: AsRef<std::path::Path>> From<T> for Id {
    fn from(value: T) -> Self {
        Id(value.as_ref().to_path_buf())
    }
}

fn main() {
    let owned_path = std::path::PathBuf::from("/some/owned/path");
    let borrowed_path = std::path::Path::new("/some/borrowed/path");
    
    let _ = Id::from(owned_path);
    let _ = Id::from(borrowed_path);
}

Playground.

This works for PathBuf as well, because it implements AsRef<Path>.

2 Likes

right, but then you clone()/to_path_buf() always (even if from() was called with PathBuf).

To sum up, I am trying to find a way to implement a (1) single From<> trait (== no copy-paste), that allows me to (2) create Id from:

  • any type that implements AsRef<std::path::Path>
  • or an owned type: std::path::PathBuf

(3) without cloning where it is not necessary (== if from() was called with PathBuf, there is no need to clone it).

Is there a way to satisfy all 3 points?

You can do this using the unstable specialization feature:

1 Like

Nice! This is the closest, so far, to what I am looking for.

But I still have to write two From<> implementations...

Well they work in different ways, so why wouldn't you? The one that avoids cloning is an optimization. Note that there may be other AsRef<Path> types where it's possible to avoid cloning as an optimization.

2 Likes

Well they work in different ways

They are, but I think from the point of view of architecture, it makes sense to encapsulate this knowledge and logic in another trait/type.
Just like it was done in Cow<> (on the conceptual level).

If I had an imaginary trait AsRefOrOwned<T>, I could have just one implementation of From

1 Like

How about this:

impl<T: Into<std::path::PathBuf>> From<T> for Id {
    fn from(value: T) -> Self {
        Id(value.into())
    }
}

An &Path implements Into<PathBuf> (by allocating), and a PathBuf implements Into<PathBuf> by returning self.

9 Likes

This is also what I would do. @dimanne note that this delegates the problem to PathBuf which has a battery of different From impls so it is not directly transferable to other similar problems.

This is beautiful! :slight_smile:

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.