Better way to get an absolute file path?

Hey everyone,
I am looking for a good best-effort conversion of a relative to absolute file path.

So far I came up with an abomination using the shellexpand crate and Path::canonicalize. Plus it doesn't look like an idiomatic Rust to me.

fn abspath(p: &str) -> Option<String> {
    shellexpand::full(p)
        .ok()
        .and_then(|x| Path::new(&x.into_owned()).canonicalize().ok())
        .and_then(|x| x.into_os_string().into_string().ok())
}

Can this code be improved? I am not worried about symlinks and hardlinks for the moment.

1 Like

Do you only need to convert relative path to the absolute one? If so you don't need shellexpand and links are dealt with by std::fs::canonicalize():

Returns the canonical, absolute form of a path with all intermediate components normalized and symbolic links resolved.

2 Likes

I am using shellexpand simply because I want the tilde (~) handled as well.

I tested my code with three paths:

  • ~/some.db
  • /Users/dimi/some.db
  • ../../some.db (the project is two levels below my homedir)

And it works and I can accept your feedback on functionality as the final stance -- but the code just looks so very ugly. I am still learning the borrow checker and it seemed to me I am fighting with it and not working with it. Am I wrong? Is the code okay then? Is there a way to do it with less chaining?

1 Like

You could:

  • change that Path::new(&x.into_owned()) to Path::new(x.as_ref()) to avoid a potential allocation
  • change what now is Path::new(x.as_ref()).canonicalize() to std::fs::canonicalize(x.as_ref()) to avoid importing Path and shortening the code a bit more
  • use the ? operator to avoid calling and_then over and over
fn abspath(p: &str) -> Option<String> {
    let exp_path = shellexpand::full(p).ok()?;
    let can_path = std::fs::canonicalize(exp_path.as_ref()).ok()?;
    can_path.into_os_string().into_string().ok()
}

I feel like this could be better if:

  • there was an implementation of AsRef<Path> for Cow<'_ str> to avoid that .as_ref()
  • there was a way to directly convert a PathBuf to a String, without passing through an OsString

However I don't think this is so bad overall

2 Likes

Chaining is fine. Here are two more idiomatic versions, though:

  • abspath() has one fewer allocation (uses as_ref() instead of into_owned());
  • abspath_buf() skips the conversion to String altogether, so that it doesn't fail unnecessarily if the result is not valid UTF-8.
fn abspath(p: &str) -> Option<String> {
    shellexpand::full(p)
        .ok()
        .and_then(|x| Path::new(OsStr::new(x.as_ref())).canonicalize().ok())
        .and_then(|p| p.into_os_string().into_string().ok())
}

fn abspath_buf(p: &str) -> Option<PathBuf> {
    shellexpand::full(p)
        .ok()
        .and_then(|x| Path::new(OsStr::new(x.as_ref())).canonicalize().ok())
}
2 Likes

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.