将错误消息写入标准错误而不是标准输出

目前,我们正在使用 println! 宏将所有输出写入终端。在大多数终端中,有两种类型的输出:标准输出 (stdout) 用于一般信息和 标准错误 (stderr) 用于错误消息。这种区分使用户可以选择将程序的成功输出定向到文件,但仍然将错误消息打印到屏幕上。

println! 宏只能打印到标准输出,所以我们需要使用其他方法来打印到标准错误。

检查错误写入的位置

首先让我们观察 minigrep 打印的内容当前是如何写入标准输出的,包括我们希望写入标准错误的任何错误消息。我们将通过将标准输出流重定向到文件并故意引发错误来实现这一点。我们不会重定向标准错误流,因此发送到标准错误的任何内容将继续显示在屏幕上。

命令行程序应该将错误消息发送到标准错误流,这样即使我们将标准输出流重定向到文件,我们仍然可以在屏幕上看到错误消息。我们的程序目前行为不当:我们将看到它将错误消息输出保存到文件中!

为了演示这种行为,我们将使用 > 和我们希望重定向标准输出流的文件路径 output.txt 来运行程序。我们不会传递任何参数,这应该会导致错误:

$ cargo run > output.txt

> 语法告诉 shell 将标准输出的内容写入 output.txt 而不是屏幕。我们没有看到预期的错误消息打印到屏幕上,这意味着它一定是在文件中了。这就是 output.txt 的内容:

Problem parsing arguments: not enough arguments

好的,我们的错误消息正在被打印到标准输出。对于这样的错误消息,将其打印到标准错误会更有用,这样只有成功运行的数据才会出现在文件中。我们将进行更改。

将错误打印到标准错误

我们将使用清单 12-24 中的代码来更改错误消息的打印方式。 由于我们在本章早些时候进行的重构,所有打印错误消息的代码都在一个函数,main 中。标准库提供了 eprintln! 宏,该宏打印到标准错误流,因此让我们将两个调用 println! 打印错误的地方改为使用 eprintln!

Filename: src/main.rs
use std::env;
use std::process;

use minigrep::Config;

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::build(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {err}");
        process::exit(1);
    });

    if let Err(e) = minigrep::run(config) {
        eprintln!("Application error: {e}");
        process::exit(1);
    }
}
Listing 12-24: Writing error messages to standard error instead of standard output using eprintln!

现在让我们以同样的方式再次运行程序,不带任何参数,并使用 > 重定向标准输出:

$ cargo run > output.txt
Problem parsing arguments: not enough arguments

现在我们在屏幕上看到错误,而output.txt没有任何内容,这是命令行程序应有的行为。

让我们再次运行程序,使用不会导致错误的参数,但仍然将标准输出重定向到文件,如下所示:

$ cargo run -- to poem.txt > output.txt

我们不会在终端看到任何输出,而output.txt将包含我们的结果:

文件名: output.txt

Are you nobody, too?
How dreary to be somebody!

这表明我们现在正在使用标准输出来输出成功的信息,并在适当的情况下使用标准错误来输出错误信息。

摘要

本章回顾了你迄今为止学到的一些主要概念,并介绍了如何在 Rust 中执行常见的 I/O 操作。通过使用命令行参数、文件、环境变量以及 eprintln! 宏来打印错误,你现在可以编写命令行应用程序。结合前几章的概念,你的代码将组织良好、在适当的数据结构中有效存储数据、优雅地处理错误,并且经过充分测试。

接下来,我们将探讨一些受函数式语言影响的 Rust 特性:闭包和迭代器。