Writing Error Messages to stderr
Instead of stdout
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 out
for general information, and standard error for error messages. This
distinction enables users to choose whether to direct a 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 out, 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 out, 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 out 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 out. 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 out 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 stderr
instead
of stdout
using eprintln!
After changing println!
to eprintln!
, let’s try running the program again
in the same way, without any arguments and redirecting stdout
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 out 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 stdout
for successful output and
stderr
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 with
stderr
, 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.