# \`into\_join\` for \`PathBuf\`

**URL:** <https://internals.rust-lang.org/t/into-join-for-pathbuf/21344>\
**Category:** language design\
**Created:** [August 11, 2024, 12:38pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344 "2024-08-11T12:38:14Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![mo8it](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mo8it/32/12423_2.png) [@mo8it](https://internals.rust-lang.org/u/mo8it)\
**Post date:** [August 11, 2024, 12:38pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/1 "2024-08-11T12:38:14Z")

</div>

It bugs me that calling [`join`](https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.join) on `PathBuf` allocates a new `PathBuf` instead of extending the existing one. Example:

```rust
returns_path_buf().join(path)

```

But I also see why people don't want to use `push` in such cases:

```rust
let mut path_buf = returns_path_buf();
path_buf.push(path);
path_buf

```

So my proposal is to add the following method to `PathBuf`:

```rust
impl PathBuf {
    #[inline]
    pub fn into_join<P: AsRef<Path>>(mut self, path: P) -> Self {
        self.push(path);
        self
    }
}

```

So the example above becomes:

```rust
returns_path_buf().into_join(path)

```

Of course, this can be chained:

```rust
returns_path_buf().into_join(path1).into_join(path2)

```

I would be happy to implement it myself and document it 🙂

---

<div class="post-metadata">

**Author:** ![chrefr](https://avatars.discourse-cdn.com/v4/letter/c/e480ec/32.png) [@chrefr](https://internals.rust-lang.org/u/chrefr)\
**Post date:** [August 11, 2024, 1:11pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/2 "2024-08-11T13:11:48Z")

</div>

Yes please.

You should open an [API Change Proposal](https://github.com/rust-lang/libs-team/issues/new?assignees=&labels=api-change-proposal%2C+T-libs-api&projects=&template=api-change-proposal.md&title=%28My+API+Change+Proposal%29).

---

<div class="post-metadata">

**Author:** ![pitaj](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pitaj/32/11262_2.png) [@pitaj](https://internals.rust-lang.org/u/pitaj)\
**Post date:** [August 11, 2024, 2:21pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/3 "2024-08-11T14:21:18Z")

</div>

Consider using the name `append` or `concat`.

---

<div class="post-metadata">

**Author:** ![kpreid](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kpreid/32/8484_2.png) [@kpreid](https://internals.rust-lang.org/u/kpreid)\
**Post date:** [August 11, 2024, 3:45pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/4 "2024-08-11T15:45:54Z")

</div>

> [@mo8it](#):
>
> Of course, this can be chained:
> 
> ```rust
> returns_path_buf().into_join(path1).into_join(path2)
> 
> ```

Note that this potentially reallocates twice, once for `path1` and once for `path2`, whereas one could define an interface that can avoid it by taking multiple inputs and counting the total length:

```rust
returns_path_buf().into_join([path1, path2])

```

This could be useful because path manipulation often looks like joining `{base_dir}/{subdir}/{filename}` (e.g. in Cargo, `{target_dir}/{profile_name}/{binary_target_name}`).

---

<div class="post-metadata">

**Author:** ![danjl1100](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/danjl1100/32/6466_2.png) [@danjl1100](https://internals.rust-lang.org/u/danjl1100)\
**Post date:** [August 11, 2024, 4:16pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/5 "2024-08-11T16:16:21Z")

</div>

To mirror @pitaj 's comment, a natural name would be `extend`.

Would this accept an `IntoIterator`?

---

<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:** [August 11, 2024, 6:08pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/6 "2024-08-11T18:08:01Z")

</div>

> [@danjl1100](#):
>
> To mirror @pitaj 's comment, a natural name would be `extend`.

Unfortunately that could cause name clashes with the [Extend](https://doc.rust-lang.org/std/iter/trait.Extend.html) trait (or more accurately, the `extend()` method within it), if a decision were ever made to implement `Extend` for `PathBuf`.

---

<div class="post-metadata">

**Author:** ![pitaj](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/pitaj/32/11262_2.png) [@pitaj](https://internals.rust-lang.org/u/pitaj)\
**Post date:** [August 11, 2024, 6:21pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/7 "2024-08-11T18:21:10Z")

</div>

[Extend is already implemented for PathBuf](https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#impl-Extend%3CP%3E-for-PathBuf)

---

<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:** [August 11, 2024, 8:33pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/8 "2024-08-11T20:33:10Z")

</div>

I do think it'd be nice to improve upon the ergonomics of `extend`. Here's a few ideas I've previously sketched out ([playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=202e79741ed04739ff1f016b7b003c62)). Note that the names of things are purely placeholders.

```rust
use std::ffi::OsStr;
use std::path::{Path, PathBuf};

fn fn_that_returns_a_pathbuf() -> PathBuf {
    PathBuf::from("root")
}

fn main() {
    let mut p = fn_that_returns_a_pathbuf().with_all((OsStr::new("a"), "b", Path::new("c")));
    println!("{}", p.display());
    p.extend_all((Path::new("d"), "e"));
    println!("{}", p.display());

    let p2 = (fn_that_returns_a_pathbuf(), "foo", OsStr::new("bar"), Path::new("baz")).combine_paths();
    println!("{}", p2.display());
}

trait ExtendAllPath {
    fn extend_all(p: &mut PathBuf, other: Self);
}
impl<T1: AsRef<Path>> ExtendAllPath for (T1,) {
    fn extend_all(p: &mut PathBuf, other: Self) {
        p.push(other.0);
    }
}
impl<T1: AsRef<Path>, T2: AsRef<Path>> ExtendAllPath for (T1,T2) {
    fn extend_all(p: &mut PathBuf, other: Self) {
        p.push(other.0);
        p.push(other.1);
    }
}
impl<T1: AsRef<Path>, T2: AsRef<Path>, T3: AsRef<Path>> ExtendAllPath for (T1,T2,T3) {
    fn extend_all(p: &mut PathBuf, other: Self) {
        p.push(other.0);
        p.push(other.1);
        p.push(other.2)
    }
}

// We're not in std so we need an extra trait to add functions to PathBuf.
trait PathBufExtendAny {
    fn extend_all<T: ExtendAllPath>(&mut self, other: T) -> &mut Self;
    fn with_all<T: ExtendAllPath>(self, other: T) -> Self;
}
impl PathBufExtendAny for PathBuf {
    fn extend_all<T: ExtendAllPath>(&mut self, other: T) -> &mut Self {
        T::extend_all(self, other);
        self
    }
    fn with_all<T: ExtendAllPath>(mut self, other: T) -> Self {
        T::extend_all(&mut self, other);
        self
    }
}

trait CombinePathTuple {
    fn combine_paths(self) -> PathBuf;
}
impl<T1: AsRef<Path>> CombinePathTuple for (PathBuf, T1) {
    fn combine_paths(mut self) -> PathBuf {
        self.0.push(self.1);
        self.0
    }
}
impl<T1: AsRef<Path>, T2: AsRef<Path>> CombinePathTuple for (PathBuf, T1, T2) {
    fn combine_paths(mut self) -> PathBuf {
        self.0.push(self.1);
        self.0.push(self.2);
        self.0
    }
}
impl<T1: AsRef<Path>, T2: AsRef<Path>, T3: AsRef<Path>> CombinePathTuple for (PathBuf, T1, T2, T3) {
    fn combine_paths(mut self) -> PathBuf {
        self.0.push(self.1);
        self.0.push(self.2);
        self.0.push(self.3);
        self.0
    }
}

```

---

<div class="post-metadata">

**Author:** ![mo8it](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mo8it/32/12423_2.png) [@mo8it](https://internals.rust-lang.org/u/mo8it)\
**Post date:** [August 12, 2024, 1:43pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/9 "2024-08-12T13:43:59Z")

</div>

> [@pitaj](#):
>
> Consider using the name `append` or `concat`.

I prefer `into_join` because it makes it obvious that the only difference to `join` is that `self` is moved.

All `append` methods in `std` take `&mut self`.

`concat` can be misleading. It implies something like string concatenation. But joining with an absolute path replaces the root path completely.

* * *

> [@kpreid](#):
>
> Note that this potentially reallocates twice, once for `path1` and once for `path2`, whereas one could define an interface that can avoid it by taking multiple inputs and counting the total length

That interface would be the equivalent of `extend` but taking ownership. I am proposing an equivalent of `push` but taking ownership. We can add both methods, but I think that the second one is more controversial. Something like this?

```rust
pub fn into_extend<S, T>(mut self, paths: S) -> Self
where
    S: AsRef<[T]>,
    T: AsRef<Path>,
{
    let paths = paths.as_ref();
    self.reserve(
        paths
            .iter()
            .map(|path| path.as_ref().as_os_str().len())
            .sum::<usize>()
            + paths.len() * const { MAIN_SEPARATOR.len_utf8() },
    );
    self.extend(paths);
    self
}

```

BTW, the current implementation of `extend` on `PathBuf` doesn't reserve capacity. It only calls `push` for every path because it takes an iterator, not a slice.

It is not always worth it to reserve capacity for all additional paths because the resulted path could be shorter in the case of something like `..` or an absolute path which replaces the root.

Anyway, let's please open a **new discussion** for this if you want to have it. I would like to keep this thread about the equivalent of `push` for only one path.

---

<div class="post-metadata">

**Author:** ![mo8it](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/mo8it/32/12423_2.png) [@mo8it](https://internals.rust-lang.org/u/mo8it)\
**Post date:** [August 12, 2024, 2:11pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/10 "2024-08-12T14:11:23Z")

</div>

> [@chrefr](#):
>
> You should open an [API Change Proposal](https://github.com/rust-lang/libs-team/issues/new?assignees=&labels=api-change-proposal%2C+T-libs-api&projects=&template=api-change-proposal.md&title=%28My+API+Change+Proposal%29).

Proposal on Github:

> <https://github.com/rust-lang/libs-team/issues/426>
>
> \# Proposal
> 
> \## Problem statement
> 
> Calling \[\`join\`\](https://doc.rust-lang.org…/stable/std/path/struct.Path.html#method.join) on \`PathBuf\` always allocates a new \`PathBuf\` instead of trying to reuse the allocation of the existing one.
> 
> \## Motivating examples or use cases
> 
> The following is used too often in the different Rust codebases that I read:
> 
> \`\`\`rust
> returns\_path\_buf().join(path)
> \`\`\`
> 
> But I see why people don't want to use \`push\` in such cases. The equivalent with \`push\` is much longer because it requires a mutable temporary variable and two additional lines:
> 
> \`\`\`rust
> let mut path\_buf = returns\_path\_buf();
> path\_buf.push(path);
> path\_buf
> \`\`\`
> 
> \## Solution sketch
> 
> My proposal is to add the following method to \`PathBuf\`:
> 
> \`\`\`rust
> impl PathBuf {
> #\[inline\]
> pub fn into\_join\<P: AsRef\<Path\>\>(mut self, path: P) -\> Self {
> self.push(path);
> self
> }
> }
> \`\`\`
> 
> So the example above becomes:
> 
> \`\`\`rust
> returns\_path\_buf().into\_join(path)
> \`\`\`
> 
> Of course, this could still allocate if the \`PathBuf\` returned by \`returns\_path\_buf\` doesn't have enough capacity. But at least it tries to use any capacity left. It is also possible that the resulted path is even shorter which means that no additional capacity is needed.
> 
> \`into\_join\` can of course be chained:
> 
> \`\`\`rust
> returns\_path\_buf().into\_join(path1).into\_join(path2)
> \`\`\`
> 
> We can add a lint to recommend using \`into\_join\` when the \`PathBuf\` isn't used anywhere afterwards.
> 
> \## Alternatives
> 
> The alternative would be to use the existing \`push\` method. But it is rarely used because \`join\` is much more ergonomic as shown in the example above.
> 
> \## Links and related work
> 
> \[Related post in the internals forum\](https://internals.rust-lang.org/t/into-join-for-pathbuf)

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [November 10, 2024, 2:12pm UTC](https://internals.rust-lang.org/t/into-join-for-pathbuf/21344/11 "2024-11-10T14:12:16Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
