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();
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.
You can use `add_extension` now.
https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.add_extension
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With