You are reading a draft of the next edition of TRPL. For more, go here.

Writing Error Messages to Standard Error Instead of Standard Output

At the moment we’re writing all of our output to the terminal with the println! function. Most terminals provide two kinds of output: standard output for general information (sometimes abbreviated as stdout in code), and standard error for error messages (stderr). This distinction enables users to choose to direct the successful output of a program to a file but still print error messages to the screen.

The println! function is only capable of printing to standard output, though, so we have to use something else in order to print to standard error.

Checking Where Errors are Written to

First, let’s observe how all content printed by minigrep is currently being written to standard output, including error messages that we want to write to standard error instead. We’ll do that by redirecting the standard output stream to a file while we also intentionally cause an error. We won’t redirect the standard error stream, so any content sent to standard error will continue to display on the screen. Command line programs are expected to send error messages to the standard error stream so that we can still see error messages on the screen even if we choose to redirect the standard output stream to a file. Our program is not currently well-behaved; we’re about to see that it saves the error message output to the file instead!

The way to demonstrate this behavior is by running the program with > and the filename, output.txt, that we want to redirect the standard output stream to. We’re not going to pass any arguments, which should cause an error:

$ cargo run > output.txt

The > syntax tells the shell to write the contents of standard output to output.txt instead of the screen. We didn’t see the error message we were expecting printed on the screen, so that means it must have ended up in the file. Let’s see what output.txt contains:

Problem parsing arguments: not enough arguments

Yup, our error message is being printed to standard output. It’s much more useful for error messages like this to be printed to standard error, and have only data from a successful run end up in the file when we redirect standard output in this way. We’ll change that.

Printing Errors to Standard Error

Let’s change how error messages are printed using the code in Listing 12-24. Because of the refactoring we did earlier in this chapter, all the code that prints error messages is in one function, in main. The standard library provides the eprintln! macro that prints to the standard error stream, so let’s change the two places we were calling println! to print errors so that these spots use eprintln! instead:

Filename: src/main.rs

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

    let config = Config::new(&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!

After changing println! to eprintln!, let’s try running the program again in the same way, without any arguments and redirecting standard output with >:

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

Now we see our error on the screen and output.txt contains nothing, which is the behavior expected of command line programs.

If we run the program again with arguments that don’t cause an error, but still redirect standard output to a file:

$ cargo run to poem.txt > output.txt

We won’t see any output to our terminal, and output.txt will contain our results:

Filename: output.txt

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

This demonstrates that we’re now using standard output for successful output and standard error for error output as appropriate.

Summary

In this chapter, we’ve recapped on some of the major concepts so far and covered how to do common I/O operations in a Rust context. By using command line arguments, files, environment variables, and the eprintln! macro for printing errors, you’re now prepared to write command line applications. By using the concepts from previous chapters, your code will be well-organized, be able to store data effectively in the appropriate data structures, handle errors nicely, and be well tested.

Next, let’s explore some functional-language influenced Rust features: closures and iterators.