# Add zero-cost builder methods to PathBuf

**URL:** <https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318>\
**Category:** libs\
**Created:** [September 10, 2021, 11:22pm UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318 "2021-09-10T23:22:40Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![SUPERCILEX](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/supercilex/32/8541_2.png) [@SUPERCILEX](https://internals.rust-lang.org/u/SUPERCILEX)\
**Post date:** [September 10, 2021, 11:22pm UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/1 "2021-09-10T23:22:40Z")

</div>

### The problem

If a user wants to create a path using a builder pattern (`path.join(a).join(b).join(c)`), they are inadvertently copying the string buffer on every call to `join`. This means to easiest way to write the code is the least efficient. The efficient version takes effort and requires the developer to either read the source code or look at the docs to know that `join` allocates memory (so they have to use `push` instead):

```rust
{
  let tmp = path.join(a);
  tmp.push(b);
  tmp.push(c);
  tmp
}

```

### Underlying issue

`join` actually comes from `Path` and allocates a new `PathBuf`. Calling `PathBuf::join` will deref coerce down to a `Path` and then come back with a newly allocated `PathBuf`.

### Potential solutions

#### New methods

Add new methods to `PathBuf` such as `PathBuf::append`, `PathBuf::using_extension`, and `PathBuf::using_file_name`.

I don't like this because it still means you have to understand the difference between `join`/`append` and why one might be better than the other.

#### Break people's code (maybe in the next edition?)

Add new methods to `PathBuf` called `PathBuf::join`, `PathBuf::with_extension`, and `PathBuf::with_file_name`. This will break people's code if they depend on `PathBuf::join` creating a new owned instance. Going back to old behavior is very easy: `(*path).join` instead of `path.join`.

Is a stdlib change like this possible? How would it work?

### Original discussion

> **[Reddit - The heart of the internet](https://www.reddit.com/r/learnrust/comments/plfqwv/is_there_a_zero_cost_builder_pattern_for_file/)**

---

<div class="post-metadata">

**Author:** ![cuviper](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cuviper/32/1897_2.png) [@cuviper](https://internals.rust-lang.org/u/cuviper)\
**Post date:** [September 10, 2021, 11:40pm UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/2 "2021-09-10T23:40:55Z")

</div>

`PathBuf` implements `FromIterator`, so `PathBuf::from_iter([a, b, c])` should work.

---

<div class="post-metadata">

**Author:** ![mbrubeck](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mbrubeck/32/174_2.png) [@mbrubeck](https://internals.rust-lang.org/u/mbrubeck)\
**Post date:** [September 11, 2021, 12:02am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/3 "2021-09-11T00:02:18Z")

</div>

Finally, a practical reason to implement `Div` for `PathBuf`, so we can join paths with `a / b / c`.

😉

(Only slightly less-controversially, we could implement `Add` for `PathBuf` and write this as `a + b + c`, as we already do for `String`.)

Note that in many cases the efficiency benefit of repeated `push` over `join` is small or even zero, since each `push` might also trigger a reallocation and copy. I expect the difference is rarely important in practice except when the number of `join` calls is fairly high.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [September 11, 2021, 12:08am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/4 "2021-09-11T00:08:46Z")

</div>

Could we perhaps impl [Concat in std::slice - Rust](https://doc.rust-lang.org/nightly/std/slice/trait.Concat.html) for `Path`s? Or maybe for `Component`s?

After all, `[a, b, c].concat()` is arguably the "best" way to do `a.to_owned() + b + c` for strings. ([Best way to do string concatenation in 2019 (Status quo) - #5 by scottmcm - help - The Rust Programming Language Forum](https://users.rust-lang.org/t/best-way-to-do-string-concatenation-in-2019-status-quo/24004/5?u=scottmcm))

---

<div class="post-metadata">

**Author:** ![Tom-Phinney](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/tom-phinney/32/3299_2.png) [@Tom-Phinney](https://internals.rust-lang.org/u/Tom-Phinney)\
**Post date:** [September 11, 2021, 12:29am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/5 "2021-09-11T00:29:11Z")

</div>

> [@mbrubeck](#):
>
> Finally, a practical reason to implement `Div` for `PathBuf` , so we can join paths with `a / b / c` .
> 
> 😉

Leading to such beauties as `a / "/" / c`.

😉

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [September 11, 2021, 2:27am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/6 "2021-09-11T02:27:07Z")

</div>

I believe it's possible to write e.g. `path.extend([a, b, c])` if you have `PathBuf` to push multiple path fragments in one go, which is another option to use.

---

<div class="post-metadata">

**Author:** ![ibraheemdev](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ibraheemdev/32/13406_2.png) [@ibraheemdev](https://internals.rust-lang.org/u/ibraheemdev)\
**Post date:** [September 11, 2021, 3:28am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/7 "2021-09-11T03:28:19Z")

</div>

> (Only slightly less-controversially, we could implement `Add` for `PathBuf` and write this as `a + b + c` , as we already do for `String` .)

This is probably much _more_ controversial 😄

---

<div class="post-metadata">

**Author:** ![SkiFire13](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/skifire13/32/7579_2.png) [@SkiFire13](https://internals.rust-lang.org/u/SkiFire13)\
**Post date:** [September 12, 2021, 8:44am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/8 "2021-09-12T08:44:37Z")

</div>

> [@cuviper](#):
>
> `PathBuf` implements `FromIterator` , so `PathBuf::from_iter([a, b, c])` should work.

This is not ideal in the case `a` is already a `PathBuf` but `b` and `c` are just `&Path`s or `&str`s

> [@CAD97](#):
>
> I believe it's possible to write e.g. `path.extend([a, b, c])` if you have `PathBuf` to push multiple path fragments in one go, which is another option to use.

Bu that's not that much better compared to repeated `push`es since it doesn't return the `PathBuf`, so you still have to use the `tmp` variable.

---

<div class="post-metadata">

**Author:** ![hyeonu](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/hyeonu/32/2162_2.png) [@hyeonu](https://internals.rust-lang.org/u/hyeonu)\
**Post date:** [September 12, 2021, 2:48pm UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/9 "2021-09-12T14:48:00Z")

</div>

Kotlin style "with" function would be a nice alternative to the builder pattern, even without those fancy syntactical support of the kotlin.

```rust
fn with<T, F: FnOnce(&mut T)>(mut base: T, modify: F) -> T {
    modify(&mut base);
    base
}

let buf = with(PathBuf::new(), |buf| {
    buf.extend([a, b, c]);
});

```

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [September 12, 2021, 5:38pm UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/10 "2021-09-12T17:38:54Z")

</div>

> [@hyeonu](#):
>
> Kotlin style "with" function

> **[tap](https://lib.rs/crates/tap)**
>
> Generic extensions for tapping values in Rust

---

<div class="post-metadata">

**Author:** ![ZyX-I](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zyx-i/32/11398_2.png) [@ZyX-I](https://internals.rust-lang.org/u/ZyX-I)\
**Post date:** [September 13, 2021, 12:00am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/11 "2021-09-13T00:00:18Z")

</div>

> [@Tom-Phinney](#):
>
> Leading to such beauties as `a / "/" / c` .

Or you can implement both Div and Add with former using `push` and latter just appending string (BTW, I do not see how to do it conveniently, and I just happened to have to do exactly that (specifically, appending extension to a file name possibly with an extension) a week ago).

---

<div class="post-metadata">

**Author:** ![bill\_myers](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bill_myers/32/4085_2.png) [@bill\_myers](https://internals.rust-lang.org/u/bill_myers)\
**Post date:** [September 14, 2021, 4:40pm UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/12 "2021-09-14T16:40:40Z")

</div>

Maybe `join`, etc. could be added to `PathBuf` and hidden in pre-2021 editions?

---

<div class="post-metadata">

**Author:** ![SUPERCILEX](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/supercilex/32/8541_2.png) [@SUPERCILEX](https://internals.rust-lang.org/u/SUPERCILEX)\
**Post date:** [September 16, 2021, 4:46am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/13 "2021-09-16T04:46:27Z")

</div>

Is that possible? Are there docs I can look at to understand how conditionally adding code by edition would work?

I think that's the best option given that the array syntax requires each path segment to be of the same type. And while the tap library looks pretty sweet (thanks for sharing!), it requires knowing how the path APIs are implemented to seek something like that out.

---

<div class="post-metadata">

**Author:** ![chris-morgan](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/chris-morgan/32/9077_2.png) [@chris-morgan](https://internals.rust-lang.org/u/chris-morgan)\
**Post date:** [September 17, 2021, 1:01am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/14 "2021-09-17T01:01:10Z")

</div>

But the truly beautiful application of `Div` is `..`:

```rust
impl Div<RangeFull> for PathBuf {
    type Output = PathBuf;
    fn div(self, _: RangeFull) -> PathBuf {
        self.pop();
        self
    }
}

```

😉

---

<div class="post-metadata">

**Author:** ![ClementNerma](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/clementnerma/32/6382_2.png) [@ClementNerma](https://internals.rust-lang.org/u/ClementNerma)\
**Post date:** [September 17, 2021, 10:37am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/15 "2021-09-17T10:37:05Z")

</div>

Maybe it's a dumb idea but why not create a `path!` macro just like how `vec!` works? Allowing to do something like:

```rust
let path = path!["dir1", "dir2", "file"];

```

Here is how it could be implemented:

```rust
macro_rules! path {
    [$($segment:expr),+] => {{
        let mut path = ::std::path::PathBuf::new();
        $(path.push($segment);)*
        path
    }}
}

```

---

<div class="post-metadata">

**Author:** ![SUPERCILEX](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/supercilex/32/8541_2.png) [@SUPERCILEX](https://internals.rust-lang.org/u/SUPERCILEX)\
**Post date:** [September 19, 2021, 12:36am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/16 "2021-09-19T00:36:43Z")

</div>

That's pretty neat. How about this to allow minimizing allocations:

```rust
macro_rules! path {
    ( $($segment:expr),+ ) => {{
        let mut path = ::std::path::PathBuf::new();
        $(path.push($segment);)*
        path
    }};
    ( $($segment:expr),+; capacity = $n:expr ) => {{
        let mut path = ::std::path::PathBuf::with_capacity($n);
        $(path.push($segment);)*
        path
    }};
}

```

Usage:

```rust
    let root: PathBuf = foo();
    let dir = "forks";
    path!(root, dir; capacity = root.as_os_str().len() + dir.len())

    // OR

    path!(root, dir)

```

---

<div class="post-metadata">

**Author:** ![ZyX-I](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zyx-i/32/11398_2.png) [@ZyX-I](https://internals.rust-lang.org/u/ZyX-I)\
**Post date:** [September 19, 2021, 12:50am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/17 "2021-09-19T00:50:21Z")

</div>

I would not say that just push is what would always be expected result for `path / ..`: `/foo/bar/..` may very well be resolved to `/baz` because `/foo/bar` is actually a symlink to `/baz/bar`. Probably better to leave this out.

---

<div class="post-metadata">

**Author:** ![jjpe](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jjpe/32/9779_2.png) [@jjpe](https://internals.rust-lang.org/u/jjpe)\
**Post date:** [September 19, 2021, 7:46am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/18 "2021-09-19T07:46:49Z")

</div>

I believe `{Path, PathBuf}` don't currently try to resolve either, favoring the verbatim path indicated by the code and leaving the resolution to other code.

If I'm correct in that assertion, then I don't see the issue with the `path!` macro as defined above.

---

<div class="post-metadata">

**Author:** ![ClementNerma](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/clementnerma/32/6382_2.png) [@ClementNerma](https://internals.rust-lang.org/u/ClementNerma)\
**Post date:** [September 19, 2021, 8:29am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/19 "2021-09-19T08:29:30Z")

</div>

`Path` and `PathBuf` simply treat paths as segments, without resolution as every push (or change in general) would require to check the filesystem for symlinks etc.

Normalization is performed with `std::fs::canonicalize` for that matter.

---

<div class="post-metadata">

**Author:** ![chrisd](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/chrisd/32/7232_2.png) [@chrisd](https://internals.rust-lang.org/u/chrisd)\
**Post date:** [September 19, 2021, 9:21am UTC](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318/20 "2021-09-19T09:21:27Z")

</div>

`Path` and `PathBuf` treat paths as an opaque blob of bytes until you call a specific function (which should document its behaviour). Most of these functions are in some way based on iterating [`components`](https://doc.rust-lang.org/std/path/struct.Path.html#method.components) but things like `push` essentially just glue bytes together. It's why `\0` in paths is valid. Or why you can `push` `.` filenames or have trailing slashes even though `components` ignores these.

Filesystem functions (some of which are implemented on `Path`) can return a new `PathBuf` but that is just whatever the OS returns after we call the relevant function.

* * *

Personally I feel there's an opportunity with `path!` macro to do some normalization or to have a compile error on "weird" paths. Though making it suitable for cross-platform code might be an interesting problem.

[Next page](https://internals.rust-lang.org/t/add-zero-cost-builder-methods-to-pathbuf/15318.md?page=2)
