10

Is there an elegant way to append a suffix such as .bak to a Path and get a new PathBuf? Something like:

let p = Path::new("foo.c");
let p_bak = /* ? */;
assert_eq!(p_bak, Path::new("foo.c.bak"));

With a string, one could use format!("{}.bak", p). With a path, I see no obvious equivalent. with_extension() doesn't quite do it, as p.with_extension("bak") will create foo.bak rather than the desired foo.c.bak.

The most "obvious" solution is to define an append_to_path() and use append_to_path(p, ".bak"):

fn append_to_path(p: &Path, s: &str) -> PathBuf {
    let mut p_osstr = p.as_os_str().to_owned();
    p_osstr.push(s);
    p_osstr.into()
}

Is there a shorter way of expressing that in one expression?

The tap crate allows streamlining the side effects into an expression, but it still feels rather cryptic:

// prior to Rust 1.70:
let p_bak: PathBuf = p.as_os_str().to_owned().tap_mut(|s| s.push(".bak")).into();

// since Rust 1.70:
let p_bak = p.to_owned().tap_mut(|s| s.as_mut_os_string().push(".bak"));
3
  • I think that's probably the best way, though I would probably implement it with an extension trait instead. Commented Nov 4, 2022 at 20:35
  • @PitaJ Agreed in general, though an extension trait is too much boilerplate if you need this in only one place. Commented Nov 5, 2022 at 9:17
  • Nightly has PathBuf::add_extension() which is useful for this. Commented Dec 11, 2024 at 12:56

2 Answers 2

6

I don't think there is any shorter way of doing it. However, we can write the function in a way that is slightly more efficient.

fn append_to_path(p: PathBuf, s: &str) -> PathBuf {
    let mut p = p.into_os_string();
    p.push(s);
    p.into()
}

into_os_string() is more efficient than as_os_str().to_owned(). into_os_string() and as_os_str() are both very cheap, but to_owned() clones the whole path, which requires a dynamic memory allocation.

However, into_os_string() can only be used on PathBuf, not on &Path, so we need to change the signature accordingly. By taking a PathBuf by value, we shift the responsibility for cloning the PathBuf to the caller, but it also enables the caller to avoid cloning if it doesn't need to.

We can also make the function more convenient to use by using the Into trait:

fn append_to_path(p: impl Into<OsString>, s: impl AsRef<OsStr>) -> PathBuf {
    let mut p = p.into();
    p.push(s);
    p.into()
}

&Path and PathBuf implement Into<OsString>, so we can pass values of these types straight to the function's first parameter. When a &Path is converted into an OsString, the Into implementation for &Path will perform a dynamic memory allocation, but when a PathBuf is converted, no memory allocation will occur. For the second parameter, I just used the same trait bounds that OsString::push has for its s parameter.

Sign up to request clarification or add additional context in comments.

1 Comment

I guess these optimizations make sense as a general Rust tip, but they fail to address the actual question, which is specifically about a shorter way of expressing this operation (i.e. avoid a utility function to begin with).
1

You can use `add_extension` now.

https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.add_extension

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.