How to get absolute path of PathBuf

I am trying to get the absolute path of PathBuf object, but I found that PathBuf doesn't have a related implement seemingly.

So I attempted to achieve it by myself,
I assume for now that the relative path here is relative to the folder path of the current caller:

use anyhow::Result;

use std::env;
use std::path::PathBuf;


fn absolute(path: PathBuf) -> Result<PathBuf> {
    let current_dir = env::current_dir()?;
    Ok(current_dir.join(path))
}

fn main() {
    let path = PathBuf::from("dir");
    let abs_path = absolute(path);
    println!("{:?}", abs_path);
} 

Output:

Ok("D:\\dev\\rustic\\del_later\\dir")

It seems to be running as I expected.

Unfortunately, when I changed the input path to:

...

fn main() {
    let path = PathBuf::from("../dir");
    ...
} 

What I got:

Ok("D:\\dev\\rustic\\del_later\\../dir")

Looks terrible. I expect to get this instead of that:

// :>
Ok("D:\\dev\\rustic\\dir")

I searched for solutions and found a popular one is using std::fs::canonicalize:

use anyhow::Result;

use std::fs;
use std::path::PathBuf;


fn absolute(path: PathBuf) -> Result<PathBuf> {
    let abs_path = fs::canonicalize(path)?;
    Ok(abs_path)
}

fn main() {
    let path = PathBuf::from("../lagerange");
    let abs_path = absolute(path);
    println!("{:?}", abs_path);
} 

Output:

Ok("\\\\?\\D:\\dev\\rustic\\lagerange")

It looks almost as expected, except for the strange part "\\\\?\\"

Actually, I noticed a topic in this community that mentioned it before, probably is Rust's unique terminology or something else. I will go over it carefully later, but that's not the point.

The point is that std::fs::canonicalize cannot handle non-existent paths.

But I don't care if the input path exists, I just need to get its absolute path.

I'd be better grateful if anyone is willing to give me an idea to achieve that.

You can just join the relative path with $CWD and then use something like clean-path to resolve the . and ...

3 Likes

Yeah, those things are unfunny. The annoying thing is that those are proper Windows paths, and the Windows API's tend to handle them gracefully. However, there are third party path processors/parsers that are naively implemented that croak when they encounter them.

I recently had need for this myself and while searching around I found a crate that looked promising. I tried to find it again, but couldn't -- however, I found path-absolutize instead, which also looks promising. Although I haven't actually tested it, the code has a bunch of MAIN_SEPARATOR, which indicates that it cares about getting the path separators right.

I have seen a few crates that claim to be able to absolutize paths, without std::fs:canonicalize(), so if you find one that doesn't behave like you want, then just keep looking.

3 Likes

Thank you for your answer about "\\\\?\\". After reading Microsoft's documentation(Naming Files, Paths, and Namespaces

), I have realized that this is in compliance with the WinAPI specification:

For file I/O, the "\?" prefix to a path string tells the Windows APIs to disable all string parsing and to send the string that follows it straight to the file system. For example, if the file system supports large paths and file names, you can exceed the MAX_PATH limits that are otherwise enforced by the Windows APIs.

Because it turns off automatic expansion of the path string, the "\?" prefix also allows the use of ".." and "." in the path names, which can be useful if you are attempting to perform operations on a file with these otherwise reserved relative path specifiers as part of the fully qualified path.

It looks very interesting.

In addition, I have tried the crate path-absolutize you mentioned and it does meet my needs:

use anyhow::{Result, bail};
use path_absolutize::Absolutize;

use std::path::PathBuf;


trait ToAbsString {
    /// Convert PathBuf to its absolute path String
    fn to_abs_string(&self) -> Result<String>;
}

impl ToAbsString for PathBuf {
    fn to_abs_string(&self) -> Result<String> {
        let abs_path = self.absolutize()?;
        let abs_path = abs_path.to_str();
        if let Some(abs_path) = abs_path {
            Ok(abs_path.to_string())
        }
        else {
            bail!("Path {} cannot be converted to string, please check for its UTF-8 validity", self.display());
        }
    }
}

fn main() {
    let path = PathBuf::from("../non_existent_dir");
    println!("{:?}", path.to_abs_string());
} 

Ok("D:\\dev\\rustic\\non_existent_dir")

Perfect ;>

By the way, I personally think that the functionality of Rust Path and PathBuf is somewhat unsatisfactory. If someone could make a crate like Python pathlib, it would be more perfect

Note that this transformation is invalid in general:

  • D:\\dev\\rustic\\del_later might not exist, so D:\\dev\\rustic\\del_later\\../dir would be invalid, but D:\\dev\\rustic\\dir might not be
  • on Unix this ignores symbolic links. For example if /foo/bar is a symbolic link to /baz/qux then /foo/bar/../quux will point to /baz/quux rather than /foo/quux

Note that pathlib has a similar behaviour:

>>> from pathlib import Path
>>> p1 = Path("E:\\foo")
>>> p2 = Path("..\\bar")
>>> Path(p1, p2)
WindowsPath('E:/foo/../bar')
>>> p1.joinpath(p2)
WindowsPath('E:/foo/../bar')

You can use .resolve() to resolve .. and symbolic links, and on Windows it seems to produce paths without the leading \\?\, however it will fail when paths are too long, which is what the \\?\ is supposed to solve.

So what exactly would you want Rust to take from pathlib?

4 Likes

Use dunce::canonicalize():

6 Likes

this will eventually be solved by Path::normalize_lexically, which has not yet been implemented to my knowledge.

4 Likes

Yes, thank for your answer. But in fact, as I mentioned, I don't care if the path exists here, because I may use the converted path as a FileNotFound error message to prompt the user. So absolute paths here are more accurate than relative paths.

You're right, pathlib also has a similar behavior in parsing relative paths, though can be solved by method resolve. But that's not the point of this topic.

Let's think back, why I tried to join them? The reason is to get the absolute path of PathBuf object. In pathlib, we can just call the method absolute

I believe that the vast majority of people use Path or PathBuf instead of str or String because they value the cross-platform compatibility and some useful methods they provide

I'm not complaining about the specifications of Windows. I'm just confused why Pathbuf lacks some really useful methods, such as absolute and glob in pathlib

I am looking forward to it :bell: Sometimes, it's risky to use external crates