Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to append to PathBuf

Tags:

path

rust

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:

// since Rust 1.70:
let p_bak = p.to_owned().tap_mut(|s| s.as_mut_os_string().push(".bak"));

// prior to Rust 1.70:
let p_bak: PathBuf = p.as_os_str().to_owned().tap_mut(|s| s.push(".bak")).into();
like image 670
user4815162342 Avatar asked Apr 29 '26 14:04

user4815162342


2 Answers

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.

like image 164
Francis Gagné Avatar answered May 03 '26 00:05

Francis Gagné


You can use `add_extension` now.

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

like image 38
Ctrlshiftm Avatar answered May 03 '26 00:05

Ctrlshiftm