有没有一种优雅的方法可以将后缀(如.bak
)附加到Path
并获得新PathBuf
?像这样:
let p = Path::new("foo.c");
let p_bak = /* ? */;
assert_eq!(p_bak, Path::new("foo.c.bak"));
使用字符串,可以使用format!("{}.bak", file_name)
.有了路径,我看不到明显的等价物。with_extension()
并没有完全做到这一点,因为p.with_extension("bak")
会创建foo.bak
而不是所需的foo.c.bak
。
最"明显"的解决方案是定义一个append_to_path()
并使用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()
}
有没有更短的表达方式?
tap
箱允许将其放在一个流线型的表达中,但它仍然感觉相当神秘:
let p_bak: PathBuf = p.as_os_str().to_owned().tap_mut(|s| s.push(".bak")).into();
我认为没有任何更短的方法可以做到这一点。但是,我们可以以稍微高效的方式编写函数。
fn append_to_path(p: PathBuf, s: &str) -> PathBuf {
let mut p = p.into_os_string();
p.push(s);
p.into()
}
into_os_string()
比as_os_str().to_owned()
更有效率。into_os_string()
和as_os_str()
都非常便宜,但to_owned()
克隆整个路径,这需要动态内存分配。
但是,into_os_string()
只能在PathBuf
上使用,不能用于&Path
,因此我们需要相应地更改签名。通过按值获取PathBuf
,我们将克隆PathBuf
的责任转移给调用方,但它也使调用方能够在不需要时避免克隆。
我们还可以通过使用Into
特性使函数更方便使用:
fn append_to_path(p: impl Into<OsString>, s: impl AsRef<OsStr>) -> PathBuf {
let mut p = p.into();
p.push(s);
p.into()
}
&Path
和PathBuf
实现Into<OsString>
,因此我们可以将这些类型的值直接传递给函数的第一个参数。当&Path
转换为OsString
时,&Path
的Into
实现将执行动态内存分配,但是当PathBuf
被转换时,不会发生内存分配。对于第二个参数,我只是使用了与OsString::push
对其s
参数相同的特征边界。
let p_bak: PathBuf = (p.to_string_lossy().to_string() + ".bak").into();
// or
let p_bak: PathBuf = PathBuf::from(p.to_string_lossy().to_string() + ".bak");
使用to_string_lossy
避免使用to_string
返回Option<&str
> 但应仅在我们知道字符串将由有效的 unicode 字符组成时才使用