Write to stderr Instead of stdout

Right now, we're writing all of our output to the terminal with println!. Most terminals provide two kinds of output: "standard out" for general information, and "standard error" for error messages. This distinction is the behavior that's expected of command line programs: it enables users to choose to direct a program's successful output to a file but still print error messages to the screen, for example. println! is only capable of printing to standard out, though, so we have to use something else in order to print to standard error.

We can verify that, the way we've written greprs so far, everything is being written to standard out, including error messages that should be written to standard error instead. We'll do that by intentionally causing an error, the one that happens when we run the program without any arguments. We're going to redirect standard output to a file, but not standard error. The way command line programs are expected to work is that, because the output is an error message, it should be shown on the screen rather than being redirected to the file. Let's see that our program is not currently meeting this expectation by using > and specifying a filename, output.txt, that we want to redirect standard out to:

$ 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:

Application error: No search string or filename found

Yup, there's our error message, which means it's being printed to standard out. This isn't what's expected from command line programs. It's much more useful for error messages like this to be printed to standard error, and only have data printed to standard out from a successful run end up in the file when we redirect standard out in this way. Let's change how error messages are printed as shown in Listing 12-23. Because of the refactoring we did earlier in this chapter, all of the code that prints error messages is in one place, in main:

Filename: src/main.rs

extern crate greprs;

use std::env;
use std::process;
use std::io::prelude::*;

use greprs::Config;

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

    let config = Config::new(&args).unwrap_or_else(|err| {
        writeln!(
            &mut stderr,
            "Problem parsing arguments: {}",
            err
        ).expect("Could not write to stderr");
        process::exit(1);
    });

    if let Err(e) = greprs::run(config) {
        writeln!(
            &mut stderr,
            "Application error: {}",
            e
        ).expect("Could not write to stderr");

        process::exit(1);
    }
}

Listing 12-23: Writing error messages to stderr instead of stdout using writeln!

Rust does not have a convenient function like println! for writing to standard error. Instead, we use the writeln! macro, which is like println! but takes an extra argument. The first thing we pass to it is what to write to. We can acquire a handle to standard error through the std::io::stderr function. We give a mutable reference to stderr to writeln!; we need it to be mutable so we can write to it! The second and third arguments to writeln! are like the first and second arguments to println!: a format string and any variables we're interpolating.

Let's try running the program again in the same way, without any arguments and redirecting stdout with >:

$ cargo run > output.txt
Application error: No search string or filename found

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

If we run the program again with arguments that don't cause an error, but still redirecting 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 standard out 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 writeln! 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.

results matching ""

    No results matching ""