[solved] Beginner: How to use std::path::Path join with arbitrary structure?

Hi, newbie here. Just started learning rust and now trying the Rocket library.
The question is inspired with a Rocket example of paste bin implementation.

We have an ID of a paste, like this

pub struct PasteID<'a>(Cow<'a,str>);

Example shows how to make a path with it

fn upload(id: PasteID) ... {
	...
  let p = format!("/upload/{id}", id=id)
  ...Path::new(p)...
  ...
}

I don't actually like that and tried this instead:

...
let p = Path::new("/dir").join(id)
...

But rust complains (playground Rust Playground)

error[E0277]: the trait bound `PasteID<'_>: std::convert::AsRef<std::path::Path>` is not satisfied
 --> src/main.rs:8:31
  |
8 |     let p = Path::new("/dir").join(id);
  |                               ^^^^ the trait `std::convert::AsRef<std::path::Path>` is not implemented for `PasteID<'_>`

What should I actually do? Really implement AsRef for PasteID?
Isn't it already implemented for Cow? If it is really needed, then how to do that correctly?

It does actually work if I cast PasteID to_string explcitely:

let p = Path::new("/dir").join(id.to_string())

but that seems weird.
So what is an idiomatic way?

1 Like

Yes it is implemented by Cow, but you dont inherit trait implementations from fields.

You can use

let p = Path::new("/dir").join(&join.0);
1 Like

Thank you for clarification. It seems &join.0 is suboptimal. I'd better use .to_string().

1 Like

If you own PasteID then why not implement AsRef<OsStr> for it? From what I can see it's just a newtype'd string, so you can defer the impl to the inner Cow<'a, str> and then you can append a PasteID to a path just fine.

You're probably looking for something like this:

impl<'a> AsRef<OsStr> for PasteID<'a> {
  fn as_ref(&self) -> &OsStr {
    self.0.as_ref()
  }
}
2 Likes

Yep, I had a hope there is something like that. I tried OsStr, but the key of the solution is to use .as_ref.
Some black magic under the hood, I guess.
The working solution Rust Playground

impl<'a> AsRef<Path> for PasteID<'a> {
  fn as_ref(&self) -> &Path {
    Path::new(self.0.as_ref())
  }
}

P.S.
(also it seems, Cow<str> could not be implicitely be converted to OsStr)

Also, I do not get why Path::new is ok for a fn returning &Path... confused

Path doesn't own any data - it's more like str, and PathBuf is more like String in this analogy. Path::new is essentially a nicely folded away transmute that "reinterprets" a reference as itself. You can see the actual code here: path.rs - source

1 Like

There's nothing implicit about it. If you look at the definition for join(), it takes something which is AsRef<Path>. Apparently a Cow<'a, OsStr> implements AsRef<Path>, but not a normal Cow<'a, str>. Hence why it's not behaving as you expect.

Most of Rust actually prefers to be explicit about something. Where "explicit" usually means there'll be something in a function signature (e.g. fn join<T: AsRef<Path>>(&self, other: T) -> PathBuf) which lets you figure out what's happening. @withoutboats has a really good article explaining the difference and Rust's philosophy.

Worth mentioning perhaps that str: AsRef<Path> and Cow<'a, str> derefs to str so that impl can be (&*self.0).as_ref() too.