许多 unwrap() 的更干净的替代方案

ale*_*nco 1 rust

以下代码_从文件夹中的 png 文件中删除该字符:

use std::fs;
use std::path::Path;

fn main() {
    let dir = Path::new("/home/alex/Desktop");
    for entry in fs::read_dir(dir).unwrap() {
        let entry = entry.unwrap();
        let path = entry.path();
        if path.is_file() && path.extension().unwrap() == "png" {
            let new_path = path.with_file_name(path.file_name().unwrap().to_str().unwrap().replace("_",""));
            fs::rename(path, new_path).unwrap();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,unwrap()它被大量使用。可以在这段代码中删除它们,并使用更干净的方法吗?

Sil*_*olo 6

您在这里用于unwrap多种不同的用途。让我们把它们分解一下。

fs::read_dir(dir).unwrap()
Run Code Online (Sandbox Code Playgroud)

read_dir如果发生 IO 错误,可能会失败。这不是你能控制的事情,也不是你能处理的事情。使用出色的令人烦恼的异常类比,这个错误将是一个外生错误:不是你的错,也不是你可以阻止的。unwrap这里是有道理的。在较大的程序中,我们可能会让我们的函数返回io::Result<_>,并可以编写fs::read_dir(dir)?让调用者尝试从错误中恢复。但对于一个小型main程序来说,unwrap这里是有意义的。

let entry = entry.unwrap();
Run Code Online (Sandbox Code Playgroud)

一样。这是你无法控制的 IO 错误。在较大的程序中,您可以编写entry?将错误传播给调用者的代码,但在这种小规模的情况下,unwrap就可以了。

path.extension().unwrap()
Run Code Online (Sandbox Code Playgroud)

这就是事情变得有趣的地方。extension不会失败。它会None在文件没有扩展名的完全正常、合理的情况下返回。例如,如果文件名为Rakefile.gitignore. 在这种情况下恐慌确实是不幸的。相反,我们只是希望该if语句失败。您的if声明现在所说的是“断言扩展存在,如果存在则执行某些操作png”。你真正想说的是“如果扩展存在并且png”。无需断言。考虑

if let Some(extension) = path.extension() {
  if extension == "png" {
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

在 Rust 的未来版本中,可以if let与 一起编写&&,因此我们可以将其缩短为

if let Some(extension) = path.extension() && extension == "png" {
  ...
}
Run Code Online (Sandbox Code Playgroud)

但该功能目前不稳定。

unwrap继续,我现在正在跳过线路,打几个电话。我们稍后会回到这个话题。

fs::rename(path, new_path).unwrap();
Run Code Online (Sandbox Code Playgroud)

fs::rename是一个 IO 操作,可能会像任何 IO 操作一样失败。让它失败,或者在包含函数的情况下传播,就像前两个一样。

现在我们来谈谈最后一行。

path.with_file_name(path.file_name().unwrap().to_str().unwrap().replace("_",""));
Run Code Online (Sandbox Code Playgroud)

file_name()None如果没有文件名则返回。在这种情况下,我们甚至不应该尝试重命名该文件,因此这应该是我们在到达这里if let 之前检查的内容。

if let Some(filename) = path.file_name() {
  ...
}
Run Code Online (Sandbox Code Playgroud)

接下来,您将使用to_str. 您需要这样做的原因是文件名使用OsStr,它可能是也可能不是有效的 UTF-8。因此,如果您想对此类文件名感到恐慌,那也没关系。就我个人而言(考虑到这种情况是多么罕见和奇怪),我可能也会恐慌(或传播,类似于其他 IO 异常)。如果您恢复,可以使用to_string_lossy,它将无效的 UTF-8 序列替换为U+FFFD

如果要传播,可以转换Optionio::Resultwith ok_or_else

最后,由于这里确实进行了大量 IO,因此我实际上建议继续将其分解为一个单独的函数,该函数会生成io::Result. 然后main可以对结果调用unwrap(或) 一次以指示任何 IO 错误,但其他调用者理论上可以处理或从这些相同的错误中恢复。expect

考虑到所有这些,我们将归结为一个expect调用main(统一)处理所有 IO 错误,如下所示。

use std::fs;
use std::io;
use std::path::Path;

fn replace_files(dir: &Path) -> io::Result<()> {
  for entry in fs::read_dir(dir)? {
    let path = entry?.path();
    if let Some(extension) = path.extension() {
      if let Some(filename) = path.file_name() {
        if path.is_file() && extension == "png" {
          let filename_utf8 =
            filename.to_str()
            .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Non-UTF-8 filename"))?;
          let new_path = path.with_file_name(filename_utf8.replace("_",""));
          fs::rename(path, new_path)?;
        }
      }
    }
  }
  Ok(())
}

fn main() {
  let dir = Path::new("/home/alex/Desktop");
  replace_files(dir).expect("I/O error occurred!");
}
Run Code Online (Sandbox Code Playgroud)

  • nit:您不需要额外的函数就可以使用 ?,`main` 也可以将 `io::Result&lt;()&gt;` 作为返回类型,并且它的错误输出可能比简单的“I /O 发生错误!”。 (2认同)