Why does joining paths completely replace the original path in Rust?
Asked Answered
E

1

6

I don't understand how Rust concatenates file paths. Why doesn't this work:

fn main() {
    let root = std::path::Path::new("resources/");
    let uri = std::path::Path::new("/js/main.js");
    let path = root.join(uri);
    assert_eq!(path.to_str(), Some("resources/js/main.js"));
}

fails with:

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `Some("/js/main.js")`,
 right: `Some("resources/js/main.js")`', src/main.rs:5:5

I see in the docs that "pushing an absolute path replaces the existing path", but this seems like a terrible idea that will catch a lot of people.

In that case, how do I safely strip the absolute path, or make it relative?

Extramural answered 22/11, 2018 at 11:50 Comment(2)
I guess you can see this: github.com/rust-lang/rust/issues/16507 discussionElimination
@Elimination thanks; I've commented on the issue.Extramural
B
9

This is because "/js/main.js" is treated as an absolute path (doc)

If path is absolute, it replaces the current path.

On Windows:

  • if path has a root but no prefix (e.g. \windows), it replaces everything except for the prefix (if any) of self.
  • if path has a prefix but no root, it replaces self.

If you change your example to "js/main.js" and then use join, it will be properly constructed (playground)

Bucktooth answered 22/11, 2018 at 12:6 Comment(10)
Thanks, then how do I treat the incoming path as always relative?Extramural
FYI: os.path.join has the same behavior in Python. I am not sure why, but at least there's some consistency there.Belamy
@PetrusTheron I would suggest not using / at all. You can omit them completly.Bucktooth
So...how do I strip the leading slash and ensure that paths are always descendants of a given folder? As you can probably tell from my example, the slash-prefix is coming from a browser. Would also like to avoid parent "../../../../some-secret" files from leaking out.Extramural
I would suggest using a crate ;) crates.io/crates/path-absolutize or crates.io/crates/path-dedotBucktooth
@Bucktooth Wouldn't path-canonicalize a more precise name? absolutize sounds to me as Make all paths absolutItinerate
@TimDiekmann quoting the doc: "The difference between absolutize and canonicalize methods is that absolutize does not care about whether the file exists and what the file really is." ;)Bucktooth
@PetrusTheron you can use Path::strip_prefix to remove the leading /: playgroundPahang
Adding on to @MatthieuM. 's assertion above about Python, in the C++ std::filesystem::path::append (it's the equivalent method used to what's happening here), this behavior is also identical. Don't put leading slashes into your path unless it's an absolute path.Kish
The current answer to the question "Why ..." is like: "because it was written this way" ("because /js is treated as absolute => according to docs it replaces"). Answers like these do not answer the question. It just re-states what was already known.Paske

© 2022 - 2024 — McMap. All rights reserved.