From b693c8400817f1022820fd63e3529cbecc35070c Mon Sep 17 00:00:00 2001 From: "Carol (Nichols || Goulding)" Date: Thu, 13 Jul 2017 18:59:38 -0400 Subject: [PATCH] Code and text changes --- second-edition/nostarch/chapter12.md | 792 ++++++++---------- second-edition/src/SUMMARY.md | 6 +- second-edition/src/ch12-00-an-io-project.md | 31 +- ...h12-01-accepting-command-line-arguments.md | 86 +- second-edition/src/ch12-02-reading-a-file.md | 54 +- ...improving-error-handling-and-modularity.md | 244 +++--- ...2-04-testing-the-librarys-functionality.md | 105 +-- ...2-05-working-with-environment-variables.md | 154 ++-- ...-06-writing-to-stderr-instead-of-stdout.md | 118 ++- 9 files changed, 734 insertions(+), 856 deletions(-) diff --git a/second-edition/nostarch/chapter12.md b/second-edition/nostarch/chapter12.md index 63c87f3b8..7ca8e763d 100644 --- a/second-edition/nostarch/chapter12.md +++ b/second-edition/nostarch/chapter12.md @@ -1,7 +1,7 @@ [TOC] -# An I/O Project Building a Small Grep +# An I/O Project: Building a Command Line Program This chapter is both a recap of the many skills you’ve learned so far and an exploration of a few more standard library features. We’re going to build a @@ -11,24 +11,25 @@ practice some of the Rust you now have under your belt. Rust’s speed, safety, *single binary* output, and cross-platform support make it a good language for creating command line tools, so for our project we’ll make our own version of the classic command line tool `grep`. Grep is an -acronym for "Globally search a Regular Expression and Print." In the simplest -use case, `grep` searches a specified file for a specified string using the -following steps: +acronym for “Globally search a Regular Expression and Print.” In the simplest +use case, `grep` searches a specified file for a specified string. To do so, +`grep` takes a filename and a string as its arguments, then reads the file and +finds lines in that file that contain the string argument. It’ll then print out +those lines. -- Take as arguments a filename and a string. -- Read the file. -- Find lines in the file that contain the string argument. -- Print out those lines. - -We'll also show how to use environment variables and print to standard error -instead of standard out; these techniques are commonly used in command line -tools. +Along the way, we’ll show how to make our command line tool use features of the +terminal that many command line tools use. We'll read the value of an +environment variable in order to allow the user to configure the behavior of +our tool. We'll print to the standard error console stream (`stderr`) instead +of standard output (`stdout`) so that, for example, the user can choose to +redirect successful output to a file while still seeing error messages on the +screen. One Rust community member, Andrew Gallant, has already created a fully-featured, very fast version of `grep`, called `ripgrep`. By comparison, -our version of `grep` will be fairly simple, this chapter will give you some of -the background knowledge to help you understand a real-world project like -`ripgrep`. +our version of `grep` will be fairly simple, but this chapter will give you +some of the background knowledge to help you understand a real-world project +like `ripgrep`. This project will bring together a number of concepts you’ve learned so far: @@ -62,26 +63,26 @@ file to search in, like so: $ cargo run searchstring example-filename.txt ``` -Right now, the program generated by `cargo new` ignores any arguments we give -it. There are some existing libraries on crates.io that can help us accept -command line arguments, but since we're learning, let's implement this +Right now, the program generated by `cargo new` cannot process arguments we +give it. There are some existing libraries on crates.io that can help us accept +command line arguments, but since you’re learning, let’s implement this ourselves. ### Reading the Argument Values -In order to be able to get the values of command line arguments passed to our -program, we'll need to call a function provided in Rust's standard library: -`std::env::args`. This function returns an *iterator* of the command line -arguments that were given to our program. We haven't discussed iterators yet, -and we'll cover them fully in Chapter 13, but for our purposes now we only need -to know two things about iterators: +We first need to make sure our program is able to get the values of command +line arguments we pass to it, for which we’ll need a function provided in +Rust’s standard library: `std::env::args`. This function returns an *iterator* +of the command line arguments that were given to our program. We haven’t +discussed iterators yet, and we’ll cover them fully in Chapter 13, but for our +purposes now we only need to know two things about iterators: Iterators produce +a series of values, and we can call the `collect` function on an iterator to +turn it into a collection, such as a vector, containing all of the elements the +iterator produces. -1. Iterators produce a series of values. -2. We can call the `collect` function on an iterator to turn it into a vector - containing all of the elements the iterator produces. - -Let's give it a try: use the code in Listing 12-1 to read any command line -arguments passed to our `minigrep` program and collect them into a vector. +Let’s give it a try: use the code in Listing 12-1 to allow your `minigrep` +program to read any command line arguments passed it and then collect the +values into a vector. Filename: src/main.rs @@ -104,24 +105,26 @@ desired function is nested in more than one module, it’s conventional to bring the parent module into scope, rather than the function itself. This lets us easily use other functions from `std::env`. It’s also less ambiguous than adding `use std::env::args;` then calling the function with just `args`; that -might look like a function that's defined in the current module. +might easily be mistaken for a function that’s defined in the current module. -> Note: `std::env::args` will panic if any argument contains invalid Unicode. -> If you need to accept arguments containing invalid Unicode, use +> ### The `args` Function and Invalid Unicode +> +> Note that `std::env::args` will panic if any argument contains invalid +> Unicode. If you need to accept arguments containing invalid Unicode, use > `std::env::args_os` instead. That function returns `OsString` values instead > of `String` values. We’ve chosen to use `std::env::args` here for simplicity > because `OsString` values differ per-platform and are more complex to work > with than `String` values. On the first line of `main`, we call `env::args`, and immediately use `collect` -to turn the iterator into a vector containing all of the iterator's values. The -`collect` function can be used to create many kinds of collections, so we -explicitly annotate the type of `args` to specify that we want a vector of -strings. Though we very rarely need to annotate types in Rust, `collect` is one -function you do often need to annotate because Rust isn't able to infer what -kind of collection you want. +to turn the iterator into a vector containing all of the values produced by the +iterator. The `collect` function can be used to create many kinds of +collections, so we explicitly annotate the type of `args` to specify that we +want a vector of strings. Though we very rarely need to annotate types in Rust, +`collect` is one function you do often need to annotate because Rust isn’t able +to infer what kind of collection you want. -Finally, we print out the vector with the debug formatter, `:?`. Let's try +Finally, we print out the vector with the debug formatter, `:?`. Let’s try running our code with no arguments, and then with two arguments: ``` @@ -134,17 +137,20 @@ $ cargo run needle haystack ``` You may notice that the first value in the vector is `"target/debug/minigrep"`, -which is the name of our binary. The reasons for this are out of the scope of -this chapter, but we'll need to remember this as we save the two arguments we -need. +which is the name of our binary. This matches the behavior of the arguments +list in C, and lets programs use the name by which they were invoked in their +execution. It's convenient to have access to the program name in case we want +to print it in messages or change behavior of the program based on what command +line alias was used to invoke the program, but for the purposes of this chapter +we're going to ignore it and only save the two arguments we need. ### Saving the Argument Values in Variables -Printing out the value of the vector of arguments just illustrated that we're -able to access the values specified as command line arguments from our program. -That's not what we actually want to do, though, we want to save the values of -the two arguments in variables so that we can use the values in our program. -Let's do that as shown in Listing 12-2: +Printing out the value of the vector of arguments has illustrated that the +program is able to access the values specified as command line arguments. +That’s not actually our end goal, though: we want to save the values of the two +arguments in variables so that we can use the values in our program. Let’s do +that as shown in Listing 12-2: Filename: src/main.rs @@ -164,9 +170,9 @@ fn main() { Listing 12-2: Create variables to hold the query argument and filename argument -As we saw when we printed out the vector, the program's name takes up the first -value in the vector at `args[0]`, so we're starting at index `1`. The first -argument `minigrep` takes is the string we're searching for, so we put a +As we saw when we printed out the vector, the program’s name takes up the first +value in the vector at `args[0]`, so that we’re starting at index `1`. The +first argument `minigrep` takes is the string we’re searching for, so we put a reference to the first argument in the variable `query`. The second argument will be the filename, so we put a reference to the second argument in the variable `filename`. @@ -183,19 +189,21 @@ Searching for test In file sample.txt ``` -Great, it's working! We're saving the values of the arguments that we need into -the right variables. Later we'll add some error handling to deal with -situations such as when the user provides no arguments, but for now we'll -ignore that and work on adding file reading capabilities instead. +Great, it’s working! The values of the arguments we need are being saved into +the right variables. Later we’ll add some error handling to deal with certain +potential erroneous situations, such as when the user provides no arguments, +but for now we’ll ignore that and work on adding file reading capabilities +instead. ## Reading a File -Next, we're going to read the file that we specify in the filename command line -argument. First, we need a sample file to test it with---the best kind of file -to use to make sure that `minigrep` is working is one with a small amount of text -over multiple lines with some repeated words. Listing 12-3 has an Emily -Dickinson poem that will work well! Create a file called `poem.txt` at the root -level of your project, and enter the poem "I'm nobody! Who are you?": +Next, we’re going to add functionality to read the file that specified in the +`filename` command line argument. First, we need a sample file to test it +with—the best kind of file to use to make sure that `minigrep` is working is +one with a small amount of text over multiple lines with some repeated words. +Listing 12-3 has an Emily Dickinson poem that will work well! Create a file +called `poem.txt` at the root level of your project, and enter the poem “I’m +nobody! Who are you?”: Filename: poem.txt @@ -225,18 +233,13 @@ use std::fs::File; use std::io::prelude::*; fn main() { - let args: Vec = env::args().collect(); - - let query = &args[1]; - let filename = &args[2]; - - println!("Searching for {}", query); + // ...snip... println!("In file {}", filename); - let mut f = File::open(filename).expect("file not found"); let mut contents = String::new(); - f.read_to_string(&mut contents).expect("something went wrong reading the file"); + f.read_to_string(&mut contents) + .expect("something went wrong reading the file"); println!("With text:\n{}", contents); } @@ -249,8 +252,8 @@ standard library: we need `std::fs::File` for dealing with files, and `std::io::prelude::*` contains various traits that are useful when doing I/O, including file I/O. In the same way that Rust has a general prelude that brings certain things into scope automatically, the `std::io` module has its own -prelude of common things you'll need when working with I/O. Unlike the default -prelude, we must explicitly `use` the prelude in `std::io`. +prelude of common things you’ll need when working with I/O. Unlike the default +prelude, we must explicitly `use` the prelude from `std::io`. In `main`, we’ve added three things: first, we get a mutable handle to the file by calling the `File::open` function and passing it the value of the `filename` @@ -259,9 +262,9 @@ mutable, empty `String`. This will hold the content of the file after we read it in. Third, we call `read_to_string` on our file handle and pass a mutable reference to `contents` as an argument. -After those lines, we've again added temporary `println!` that prints out the -value in `contents` after we've read the file so we can check that our program -is working so far. +After those lines, we’ve again added a temporary `println!` statement that +prints out the value of `contents` after the file is read, so that we can check +that our program is working so far. Let’s try running this code with any string as the first command line argument (since we haven’t implemented the searching part yet) and our *poem.txt* file @@ -285,13 +288,14 @@ To tell your name the livelong day To an admiring bog! ``` -Great! Our code read in and printed out the content of the file. We've got a -few flaws though: the `main` function has multiple responsibilities, and we're -not handling errors as well as we could be. While our program is still small, -these flaws aren't a big problem, but as our program grows, it will be harder -to fix them cleanly. It's good practice to begin refactoring early on when -developing a program, as it's much easier to refactor smaller amounts of code, -so we'll do that now. +Great! Our code read in and printed out the content of the file. We’ve got a +few flaws though. The `main` function has multiple responsibilities; generally +functions are clearer and easier to maintain if each function is responsible +for only one idea. The other problem is that we’re not handling errors as well +as we could be. While our program is still small, these flaws aren’t a big +problem, but as our program grows, it will be harder to fix them cleanly. It’s +good practice to begin refactoring early on when developing a program, as it’s +much easier to refactor smaller amounts of code, so we’ll do that now. ## Refactoring to Improve Modularity and Error Handling @@ -314,12 +318,12 @@ we’re going to need to bring into scope; the more variables we have in scope, the harder it is to keep track of the purpose of each. It’s better to group the configuration variables into one structure to make their purpose clear. -The third problem is that we've used `expect` to print out an error message if -opening the file fails, but the error message only says `file not found`. There -are a number of ways that opening a file can fail besides a missing file: for -example, the file might exist, but we might not have permission to open it. -Right now, if we're in that situation, we'd print the `file not found` error -message that would give the user the wrong advice! +The third problem is that we’ve used `expect` to print out an error message +when opening the file fails, but the error message only says `file not found`. +There are a number of ways that opening a file can fail besides the file being +missing: for example, the file might exist, but we might not have permission to +open it. Right now, if we’re in that situation, we’d print the `file not found` +error message that would give the user the wrong advice! Fourth, we use `expect` repeatedly to deal with different errors, and if the user runs our programs without specifying enough arguments, they’ll get an @@ -334,23 +338,23 @@ Let’s address these problems by refactoring our project. ### Separation of Concerns for Binary Projects -The organizational problem of having the `main` function responsible for -multiple tasks is common to many binary projects, so the Rust community has -developed a kind of guideline process for splitting up the separate concerns of -a binary program when `main` starts getting large. The process has the -following steps: +The organizational problem of allocating responsibility for multiple tasks to +the `main` function responsible is common to many binary projects, so the Rust +community has developed a kind of guideline process for splitting up the +separate concerns of a binary program when `main` starts getting large. The +process has the following steps: -1. Split your program into both a *main.rs* and a *lib.rs* and move your - program's logic into *lib.rs*. -2. While your command line parsing logic is small, it can remain in *main.rs*. -3. When the command line parsing logic starts getting complicated, extract it - from *main.rs* into *lib.rs* as well. -4. The responsibilities that remain in the `main` function after this process - should be: - * Calling the command line parsing logic with the argument values - * Setting up any other configuration - * Calling a `run` function in *lib.rs* - * If `run` returns an error, handling that error +* Split your program into both a *main.rs* and a *lib.rs* and move your +program’s logic into *lib.rs*. +* While your command line parsing logic is small, it can remain in *main.rs*. +* When the command line parsing logic starts getting complicated, extract it +from *main.rs* into *lib.rs* as well. +* The responsibilities that remain in the `main` function after this process +should be limited to: + * Calling the command line parsing logic with the argument values + * Setting up any other configuration + * Calling a `run` function in *lib.rs* + * If `run` returns an error, handling that error This pattern is all about separating concerns: *main.rs* handles running the program, and *lib.rs* handles all of the logic of the task at hand. Because we @@ -359,7 +363,7 @@ program’s logic by moving it into functions in *lib.rs*. The only code that remains in *main.rs* will be small enough to verify its correctness by reading it. Let’s re-work our program by following this process. -### Extracting the Argument Parser +#### Extracting the Argument Parser First, we’ll extract the functionality for parsing arguments. Listing 12-5 shows the new start of `main` that calls a new function `parse_config`, which @@ -390,10 +394,10 @@ We’re still collecting the command line arguments into a vector, but instead o assigning the argument value at index `1` to the variable `query` and the argument value at index `2` to the variable `filename` within the `main` function, we pass the whole vector to the `parse_config` function. The -`parse_config` function then holds the logic that knows which argument goes in -which variable, and passes the values back to `main`. We still create the -`query` and `filename` variables in `main`, but `main` no longer has the -responsibility of knowing how the command line arguments and variables +`parse_config` function then holds the logic that determines which argument +goes in which variable, and passes the values back to `main`. We still create +the `query` and `filename` variables in `main`, but `main` no longer has the +responsibility of determining how the command line arguments and variables correspond. This may seem like overkill for our small program, but we’re refactoring in @@ -474,7 +478,7 @@ since we don’t have to manage the lifetimes of the references, so in this circumstance giving up a little performance to gain simplicity is a worthwhile trade-off. -> #### The Tradeoffs of Using `clone` +> ### The Tradeoffs of Using `clone` > > There’s a tendency among many Rustaceans to avoid using `clone` to fix > ownership problems because of its runtime cost. In Chapter 13 on iterators, @@ -486,15 +490,15 @@ trade-off. > Rust, it’ll be easier to go straight to the desirable method, but for now it’s > perfectly acceptable to call `clone`. -We've updated `main` so that it places the instance of `Config` that -`parse_config` returns into a variable named `config`, and updated the code -that previously used the separate `query` and `filename` variables so that is -now uses the fields on the `Config` struct instead. +We’ve updated `main` so that it places the instance of `Config` returned by +`parse_config` into a variable named `config`, and updated the code that +previously used the separate `query` and `filename` variables so that it now +uses the fields on the `Config` struct instead. -Our code now more clearly conveys our intent that `query` and `filename` are -related and their purpose is to configure how the program will work. Any code -that uses these values knows to find them in the `config` instance in the -fields named for their purpose. +Our code now more clearly conveys that `query` and `filename` are related and +their purpose is to configure how the program will work. Any code that uses +these values knows to find them in the `config` instance in the fields named +for their purpose. #### Creating a Constructor for `Config` @@ -510,9 +514,9 @@ instance, we can change `parse_config` from being a plain function into a function named `new` that is associated with the `Config` struct. Making this change will make our code more idiomatic: we can create instances of types in the standard library like `String` by calling `String::new`, and by changing -`parse_config` to be a `new` function associated with `Config`, we'll be able -to create instances of `Config` by calling `Config::new`. Listing 12-7 shows -the changes we'll need to make: +`parse_config` into a `new` function associated with `Config`, we’ll be able to +create instances of `Config` by calling `Config::new`. Listing 12-7 shows the +changes we’ll need to make: Filename: src/main.rs @@ -560,16 +564,16 @@ but the index is 1', /stable-dist-rustc/build/src/libcollections/vec.rs:1307 note: Run with `RUST_BACKTRACE=1` for a backtrace. ``` -`index out of bounds: the len is 1 but the index is 1` is an error message that -is intended for programmers, and won't really help our end users understand -what happened and what they should do instead. Let's fix that now. +The line that states `index out of bounds: the len is 1 but the index is 1` is +an error message intended for programmers, and won’t really help our end users +understand what happened and what they should do instead. Let’s fix that now. #### Improving the Error Message -In Listing 12-8, we're adding a check in the `new` function to check that the -slice is long enough before accessing index 1 and 2. If the slice isn't long -enough, we panic with a better error message than the `index out of bounds` -message: +In Listing 12-8, we’re adding a check in the `new` function that will check +that the slice is long enough before accessing index `1` and `2`. If the slice +isn’t long enough, the program panics, with a better error message than the +`index out of bounds` message: Filename: src/main.rs @@ -584,13 +588,13 @@ fn new(args: &[String]) -> Config { Listing 12-8: Adding a check for the number of arguments -This is similar to the `Guess::new` function we wrote in Listing 9-8, where we -called `panic!` if the `value` argument was out of the range of valid values. -Instead of checking for a range of values, we're checking that the length of -`args` is at least 3, and the rest of the function can operate under the -assumption that this condition has been met. If `args` has fewer than 3 items, -this condition will be true, and we call the `panic!` macro to end the program -immediately. +This is similar to the `Guess::new` function we wrote in Listing 9-8, where +`panic!` was called when the `value` argument was out of the range of valid +values. Instead of checking for a range of values here, we’re checking that the +length of `args` is at least 3, and the rest of the function can operate under +the assumption that this condition has been met. If `args` has fewer than 3 +items, this condition will be true, and we call the `panic!` macro to end the +program immediately. With these extra few lines of code in `new`, let’s try running our program without any arguments again and see what the error looks like now: @@ -607,22 +611,22 @@ This output is better, we now have a reasonable error message. However, we also have a bunch of extra information we don’t want to give to our users. So perhaps using the technique we used in Listing 9-8 isn’t the best to use here; a call to `panic!` is more appropriate for a programming problem rather than a -usage problem anyway, as we discussed in Chapter 9. Instead, we can use the -other technique we learned about in that chapter: returning a `Result` that can +usage problem, as we discussed in Chapter 9. Instead, we can use the other +technique you also learned about in Chapter 9: returning a `Result` that can indicate either success or an error. #### Returning a `Result` from `new` Instead of Calling `panic!` We can choose to instead return a `Result` value that will contain a `Config` instance in the successful case, and will describe the problem in the error -case. When `Config::new` is communicating to `main`, we can use Rust's way of -signaling that there was a problem using the `Result` type. Then we can change -`main` to convert an `Err` variant into a nicer error for our users, without -the surrounding text about `thread 'main'` and `RUST_BACKTRACE` that a call to +case. When `Config::new` is communicating to `main`, we can use the `Result` +type to signal that there was a problem. Then we can change `main` to convert +an `Err` variant into a more practical error for our users, without the +surrounding text about `thread 'main'` and `RUST_BACKTRACE` that a call to `panic!` causes. -Listing 12-9 shows the changes to the return value of `Config::new` and the -body of the function needed to return a `Result`: +Listing 12-9 shows the changes you need to make to the return value of +`Config::new` and the body of the function needed to return a `Result`: Filename: src/main.rs @@ -660,11 +664,11 @@ more cleanly in the error case. #### Calling `Config::new` and Handling Errors In order to handle the error case and print a user-friendly message, we need to -update `main` to handle the `Result` that `Config::new` is now returning as -shown in Listing 12-10. We're also going to implement by hand something that -`panic!` handled for us: exiting the command line tool with an error code of 1. -A nonzero exit status is a convention to signal to the process that called our -program that our program ended with an error state. +update `main` to handle the `Result` being returned by `Config::new`, as shown +in Listing 12-10. We’re also going to take the responsibility of exiting the +command line tool with a nonzero error code from `panic!` and implement it by +hand. A nonzero exit status is a convention to signal to the process that +called our program that our program ended with an error state. Filename: src/main.rs @@ -699,12 +703,11 @@ pipes. The code in the closure can then use the `err` value when it runs. We’ve added a new `use` line to import `process` from the standard library. The code in the closure that will get run in the error case is only two lines: we -print out the `err` value, then call `std::process::exit` (we've added a new -`use` line at the top to import `process` from the standard library). -`process::exit` will stop the program immediately and return the number that -was passed as the exit status code. This is similar to the `panic!`-based -handling we used in Listing 12-8, with the exception that we no longer get all -the extra output. Let's try it: +print out the `err` value, then call `process::exit`. The `process::exit` +function will stop the program immediately and return the number that was +passed as the exit status code. This is similar to the `panic!`-based handling +we used in Listing 12-8, with the exception that we no longer get all the extra +output. Let’s try it: ``` $ cargo run @@ -716,18 +719,18 @@ Problem parsing arguments: not enough arguments Great! This output is much friendlier for our users. -### Extracting a `run` Function +### Extracting Logic from `main` -Now we're done refactoring our configuration parsing; let's turn to our -program's logic. As we laid out in the process we discussed in the "Separation -of Concerns for Binary Projects" section, we're going to extract a function -named `run` that will hold all of the logic currently in the `main` function -that isn't setting up configuration or handling errors. Once we're done, `main` -will be concise and easy to verify by inspection, and we'll be able to write -tests for all of the other logic. +Now we’re done refactoring our configuration parsing; let’s turn to our +program’s logic. As we laid out in the “Separation of Concerns for Binary +Projects” section, we’re going to extract a function named `run` that will hold +all of the logic currently in the `main` function not involved with setting up +configuration or handling errors. Once we’re done, `main` will be concise and +easy to verify by inspection, and we’ll be able to write tests for all of the +other logic. -Listing 12-11 shows the extracted `run` function. For now, we're making only -the small, incremental improvement of extracting the function and still +Listing 12-11 shows the extracted `run` function. For now, we’re making only +the small, incremental improvement of extracting the function. We’re still defining the function in *src/main.rs*: Filename: src/main.rs @@ -764,13 +767,13 @@ argument. #### Returning Errors from the `run` Function -With the remaining program logic separated into the `run` function rather than -being in `main`, we can improve the error handling like we did with -`Config::new` in Listing 12-9. Instead of allowing the program to panic by -calling `expect`, the `run` function will return a `Result` when -something goes wrong. This will let us further consolidate the logic around -handling errors in a user-friendly way into `main`. Listing 12-12 shows the -changes to the signature and body of `run`: +With the remaining program logic separated into the `run` function, we can +improve the error handling like we did with `Config::new` in Listing 12-9. +Instead of allowing the program to panic by calling `expect`, the `run` +function will return a `Result` when something goes wrong. This will let +us further consolidate the logic around handling errors in a user-friendly way +into `main`. Listing 12-12 shows the changes you need to make to the signature +and body of `run`: Filename: src/main.rs @@ -834,8 +837,8 @@ to have some error handling code here! Let’s rectify that now. #### Handling Errors Returned from `run` in `main` -We'll check for errors and handle them nicely using a similar technique to the -way we handled errors with `Config::new` in Listing 12-10, but with a slight +We’ll check for errors and handle them using a similar technique to the way we +handled errors with `Config::new` in Listing 12-10, but with a slight difference: Filename: src/main.rs @@ -865,21 +868,22 @@ value as it would only be `()`. The bodies of the `if let` and the `unwrap_or_else` functions are the same in both cases though: we print out the error and exit. -### Split Code into a Library Crate +### Splitting Code into a Library Crate This is looking pretty good so far! Now we’re going to split the *src/main.rs* file up and put some code into *src/lib.rs* so that we can test it and have a -small `main` function. +*src/main.rs* file with fewer responsibilities. -Let's move the following pieces of code from *src/main.rs* to a new file, -*src/lib.rs*: +Let’s move everything that isn't the `main` function from *src/main.rs* to a +new file, *src/lib.rs*: * The `run` function definition * The relevant `use` statements * The definition of `Config` * The `Config::new` function definition -The contents of *src/lib.rs* should now look like Listing 12-13: +The contents of *src/lib.rs* should have the signatures shown in Listing 12-13 +(we've omitted the bodies of the functions for brevity): Filename: src/lib.rs @@ -895,26 +899,12 @@ pub struct Config { impl Config { pub fn new(args: &[String]) -> Result { - if args.len() < 3 { - return Err("not enough arguments"); - } - - let query = args[1].clone(); - let filename = args[2].clone(); - - Ok(Config { query, filename }) + // ...snip... } } -pub fn run(config: Config) -> Result<(), Box>{ - let mut f = File::open(config.filename)?; - - let mut contents = String::new(); - f.read_to_string(&mut contents)?; - - println!("With text:\n{}", contents); - - Ok(()) +pub fn run(config: Config) -> Result<(), Box> { + // ...snip... } ``` @@ -922,14 +912,12 @@ Listing 12-13: Moving `Config` and `run` into *src/lib.rs* We’ve made liberal use of `pub` here: on `Config`, its fields and its `new` method, and on the `run` function. We now have a library crate that has a -public API that we can test. - -#### Calling the Library Crate from the Binary Crate +public API that we can test! Now we need to bring the code we moved to *src/lib.rs* into the scope of the -binary crate in *src/main.rs* by using `extern crate minigrep`. Then we’ll add a -`use minigrep::Config` line to bring the `Config` type into scope, and prefix the -`run` function with our crate name as shown in Listing 12-14: +binary crate in *src/main.rs* by using `extern crate minigrep`. Then we’ll add +a `use minigrep::Config` line to bring the `Config` type into scope, and prefix +the `run` function with our crate name as shown in Listing 12-14: Filename: src/main.rs @@ -942,28 +930,20 @@ use std::process; use minigrep::Config; fn main() { - let args: Vec = env::args().collect(); - - let config = Config::new(&args).unwrap_or_else(|err| { - println!("Problem parsing arguments: {}", err); - process::exit(1); - }); - - println!("Searching for {}", config.query); - println!("In file {}", config.filename); - + // ...snip... if let Err(e) = minigrep::run(config) { - println!("Application error: {}", e); - - process::exit(1); + // ...snip... } } ``` Listing 12-14: Bringing the `minigrep` crate into the scope of *src/main.rs* -With that, all the functionality should be connected and should work. Give it a -`cargo run` and make sure everything is wired up correctly. +To bring the library crate into the binary crate, we use `extern crate` +`minigrep`. Then we’ll add a `use` `minigrep``::Config` line to bring the +`Config` type into scope, and we'll prefix the `run` function with our crate +name. With that, all the functionality should be connected and should work. +Give it a `cargo run` and make sure everything is wired up correctly. Whew! That was a lot of work, but we’ve set ourselves up for success in the future. Now it’s much easier to handle errors, and we’ve made our code more @@ -985,21 +965,21 @@ In this section, we’re going to follow the Test Driven Development (TDD) process. This is a software development technique that follows this set of steps: -1. Write a test that fails, and run it to make sure it fails for the reason - you expected. -2. Write or modify just enough code to make the new test pass. -3. Refactor the code you just added or changed, and make sure the tests - continue to pass. -4. Repeat! +* Write a test that fails, and run it to make sure it fails for the reason you +expected. +* Write or modify just enough code to make the new test pass. +* Refactor the code you just added or changed, and make sure the tests continue +to pass. +* Repeat! This is just one of many ways to write software, but TDD can help drive the -design of code. Writing the test before writing the code that makes the test +design of code. Writing the test before you write the code that makes the test pass helps to maintain high test coverage throughout the process. -We're going to test drive the implementation of the part of our `minigrep` -program that will actually do the searching for the query string in the file -contents and produce a list of lines that match the query. We're going to add -this functionality in a function called `search`. +We’re going to test drive the implementation of the functionality that will +actually do the searching for the query string in the file contents and produce +a list of lines that match the query. We’re going to add this functionality in +a function called `search`. ### Writing a Failing Test @@ -1035,28 +1015,27 @@ Pick three."; Listing 12-15: Creating a failing test for the `search` function we wish we had -We've chosen to use “duct” as the string we're looking for in this test. The -text we're searching in is three lines, only one of which contains “duct”. We -assert that the value returned from the `search` function contains only the one -line we expect. +The string we are searching for is “duct” in this test. The text we’re +searching is three lines, only one of which contains “duct”. We assert that the +value returned from the `search` function contains only the line we expect. -We aren't able to run this test and watch it fail though, since this test -doesn't even compile yet! We're going to add just enough code to get it to -compile: a definition of the `search` function that always returns an empty -vector, as shown in Listing 12-16. Once we have this, the test should compile -and fail because an empty vector doesn't match a vector containing the one -line `"safe, fast, productive."`. +We aren’t able to run this test and watch it fail though, since this test +doesn’t even compile–the search function doesn't exist yet! So now we’ll add +just enough code to get the tests to compile and run: a definition of the +`search` function that always returns an empty vector, as shown in Listing +12-16. Once we have this, the test should compile and fail because an empty +vector doesn’t match a vector containing the line `"safe, fast, productive."`. Filename: src/lib.rs ``` -fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { - vec![] +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + vec![] } ``` -Listing 12-16: Defining just enough of the `search` function that our test will -compile +Listing 12-16: Defining just enough of the `search` function so that our test +will compile Notice that we need an explicit lifetime `'a` defined in the signature of `search` and used with the `contents` argument and the return value. Remember @@ -1109,7 +1088,7 @@ test test::one_result ... FAILED failures: ---- test::one_result stdout ---- - thread 'test::one_result' panicked at 'assertion failed: `(left == right)` + thread 'test::one_result' panicked at 'assertion failed: `(left == right)` (left: `["safe, fast, productive."]`, right: `[]`)', src/lib.rs:16 note: Run with `RUST_BACKTRACE=1` for a backtrace. @@ -1124,16 +1103,16 @@ error: test failed Great, our test fails, exactly as we expected. Let’s get the test to pass! -### Writing Code that Gets the Test to Pass +### Writing Code to Pass the Test Currently, our test is failing because we always return an empty vector. To fix that and implement `search`, our program needs to follow these steps: -1. Iterate through each line of the contents. -2. Check if the line contains our query string. - * If it does, add it to the list of values we're returning. - * If it doesn't, do nothing. -3. Return the list of results that match. +* Iterate through each line of the contents. +* Check if the line contains our query string. +* If it does, add it to the list of values we’re returning. +* If it doesn’t, do nothing. +* Return the list of results that match. Let’s take each step at a time, starting with iterating through lines. @@ -1145,7 +1124,7 @@ conveniently named `lines`, that works as shown in Listing 12-17: Filename: src/lib.rs ``` -fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { for line in contents.lines() { // do something with line } @@ -1169,7 +1148,7 @@ Listing 12-18: Filename: src/lib.rs ``` -fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { for line in contents.lines() { if line.contains(query) { // do something with line @@ -1191,7 +1170,7 @@ vector, as shown in Listing 12-19: Filename: src/lib.rs ``` -fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { @@ -1215,28 +1194,16 @@ running 1 test test test::one_result ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured - - Running target/debug/minigrep-2f55ee8cd1721808 - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured - - Doc-tests minigrep - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured ``` Our test passed, great, it works! Now that our test is passing, we could consider opportunities for refactoring -the implementation of the `search` function while keeping the tests passing in -order to maintain the same functionality while we do so. This code isn't bad, -but it isn't taking advantage of some useful features of iterators. We'll be -coming back to this example in Chapter 13 where we'll explore iterators in -detail and see how to improve it. +the implementation of the `search` function while keeping the code that passes +the tests, in order to maintain the same functionality. The code in the +`search` function isn’t too bad, but it isn’t taking advantage of some useful +features of iterators. We’ll be coming back to this example in Chapter 13 where +we’ll explore iterators in detail and see how to improve it. #### Using the `search` Function in the `run` Function @@ -1262,8 +1229,8 @@ pub fn run(config: Config) -> Result<(), Box> { } ``` -We're again using a `for` loop to get each line returned from `search`, and -the code that we run for each line prints it out. +We’re still using a `for` loop to get each line returned from `search` and +printing out each line. Now our whole program should be working! Let’s try it out, first with a word that should return exactly one line from the Emily Dickinson poem, “frog”: @@ -1295,34 +1262,34 @@ $ cargo run monomorphization poem.txt Running `target/debug/minigrep monomorphization poem.txt` ``` -Excellent! We've built our own version of a classic tool, and learned a lot -about how to structure applications. We've also learned a bit about file input -and output, lifetimes, testing, and command line parsing. +Excellent! We’ve built our own mini version of a classic tool, and learned a +lot about how to structure applications. We’ve also learned a bit about file +input and output, lifetimes, testing, and command line parsing. -Feel free to move on to Chapter 13 if you'd like at this point. To round out -this project chapter, though, we're going to briefly demonstrate how to work -with environment variables and printing to standard error, both of which are -useful when writing command line programs. +To round out this project chapter, we’re going to briefly demonstrate how to +work with environment variables and how to print to standard error, both of +which are useful when writing command line programs. Feel free to move on to +Chapter 13 if you’d like at this point. ## Working with Environment Variables -We're going to improve our tool with an extra feature: an option for case -insensitive searching turned on via an environment variable. We could make this -a command line option and require that users enter it each time they want it to -apply, but instead we're going to use an environment variable. This allows our -users to set the environment variable once and have all their searches be case -insensitive in that terminal session. +We’re going to improve our tool with an extra feature: an option for case +insensitive searching that the user can turn on via an environment variable. We +could make this a command line option and require that users enter it each time +they want it to apply, but instead we’re going to use an environment variable. +This allows our users to set the environment variable once and have all their +searches be case insensitive in that terminal session. ### Writing a Failing Test for the Case-Insensitive `search` Function -First, let's add a new function that we will call when the environment variable -is on. +We want to add a new `search_case_insensitive` function that we will call when +the environment variable is on. -We're going to continue following the TDD process that we started doing in the -last section, and the first step is again to write a failing test. We'll add a -new test for the new case insensitive search function, and rename our old test -from `one_result` to `case_sensitive` to be clearer about the differences -between the two tests, as shown in Listing 12-20: +We’re going to continue following the TDD process, so the first step is again +to write a failing test. We’ll add a new test for the new case-insensitive +search function, and rename our old test from `one_result` to `case_sensitive` +to be clearer about the differences between the two tests, as shown in Listing +12-20: Filename: src/lib.rs @@ -1366,30 +1333,29 @@ Trust me."; Listing 12-20: Adding a new failing test for the case insensitive function we’re about to add -Note that we've edited the old test's `contents` too. We've added a new line -with the text "Duct tape", with a capital D, that shouldn't match the query -"duct" when we're searching for the query in a case sensitive manner. We've -changed this test to ensure that we don't accidentally break the case sensitive -search functionality that we've already implemented; this test should pass now +Note that we’ve edited the old test’s `contents` too. We've added a new line +with the text “Duct tape”, with a capital D, that shouldn’t match the query +“duct” when we’re searching in a case sensitive manner. Changing the old test +in this way helps ensure that we don’t accidentally break the case sensitive +search functionality that we’ve already implemented; this test should pass now and should continue to pass as we work on the case insensitive search. -The new test for the case insensitive search uses "rUsT" with some capital -letters as its query. The expected return value from the -`search_case_insensitive` function we're going to add is that the query "rust" -will match both the line containing "Rust:" with a capital R and also the line -"Trust me." that contains "rust" with a lowercase r. This test will fail to -compile right now since we haven't yet defined the `search_case_insensitive` -function; feel free to add a skeleton implementation that always returns an -empty vector in the same way that we did for the `search` function in Listing -12-16 in order to see the test compile and fail. +The new test for the case *insensitive* search uses “rUsT” as its query. In the +`search_case_insensitive` function we’re going to add, the query “rUsT” should +match both the line containing “Rust:” with a capital R and also the line +“Trust me.” even though both of those have different casing than the query. +This is our failing test, and it will fail to compile because we haven’t yet +defined the `search_case_insensitive` function. Feel free to add a skeleton +implementation that always returns an empty vector in the same way that we did +for the `search` function in Listing 12-16 in order to see the test compile and +fail. ### Implementing the `search_case_insensitive` Function The `search_case_insensitive` function, shown in Listing 12-21, will be almost -the same as the `search` function. The difference is that we'll lowercase the -`query` function and each `line` so that whatever the case of the input -arguments, they will be the same case when we check whether the line contains -the query. +the same as the `search` function. The only difference is that we’ll lowercase +the `query` and each `line` so that whatever the case of the input arguments, +they will be the same case when we check whether the line contains the query. Filename: src/lib.rs @@ -1417,42 +1383,26 @@ matter if the user’s query is “rust”, “RUST”, “Rust”, or “rUsT the query as if it was “rust” and be insensitive to the case. Note that `query` is now a `String` rather than a string slice, because calling -`to_lowercase` is creating new data, not referencing existing data. If the -query is "rUsT", that string slice does not contain a lowercase u or t for us -to use, so we have to allocate a new `String` containing "rust". Because -`query` is now a `String`, when we pass `query` as an argument to the -`contains` method, we need to add an ampersand since the signature of -`contains` is defined to take a string slice. +`to_lowercase` creates new data rather than referencing existing data. Say the +query is “rUsT”, as an example: that string slice does not contain a lowercase +“u” or “t” for us to use, so we have to allocate a new `String` containing +“rust”. When we pass `query` as an argument to the `contains` method now, we +need to add an ampersand because the signature of `contains` is defined to take +a string slice. Next, we add a call to `to_lowercase` on each `line` before we check if it -contains `query`. This will turn "Rust:" into "rust:" and "Trust me." into -"trust me." Now that we've converted both `line` and `query` to all lowercase, -we'll find matches no matter what case the text in the file has or the user -entered in the query. +contains `query` to lowercase all characters. Now that we’ve converted both +`line` and `query` to lowercase, we’ll find matches no matter what the case of +the query. Let’s see if this implementation passes the tests: ``` - Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs - Running target/debug/deps/minigrep-e58e9b12d35dc861 - running 2 tests test test::case_insensitive ... ok test test::case_sensitive ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured - - Running target/debug/minigrep-8a7faa2662b5030a - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured - - Doc-tests minigrep - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured ``` Great! Now, let’s actually call the new `search_case_insensitive` function from @@ -1503,10 +1453,10 @@ the value in `config.case_sensitive` Finally, we need to actually check for the environment variable. The functions for working with environment variables are in the `env` module in the standard -library, so we want to bring that module into scope with a `use std::env;` -line at the top of *src/lib.rs*. Then we're going to use the `var` method -from the `env` module in `Config::new` to check for an environment variable -named `CASE_INSENSITIVE`, as shown in Listing 12-23: +library, so we want to bring that module into scope with a `use std::env;` line +at the top of *src/lib.rs*. Then we’re going to use the `var` method from the +`env` module to check for an environment variable named `CASE_INSENSITIVE`, as +shown in Listing 12-23: Filename: src/lib.rs @@ -1526,11 +1476,7 @@ impl Config { let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); - Ok(Config { - query: query, - filename: filename, - case_sensitive: case_sensitive, - }) + Ok(Config { query, filename, case_sensitive }) } } ``` @@ -1538,24 +1484,27 @@ impl Config { Listing 12-23: Checking for an environment variable named `CASE_INSENSITIVE` Here, we create a new variable `case_sensitive`. In order to set its value, we -call the `env::var` function and pass it the name of the environment variable -we're looking for, `CASE_INSENSITIVE`. `env::var` returns a `Result` that will -be the `Ok` variant containing the value if the environment variable is set, -and will be the `Err` variant if the environment variable is not set. We're -using the `is_err` method on the `Result` to check to see if it's an error (and -therefore unset), which means we *should* do a case sensitive search. If the -`CASE_INSENSITIVE` environment variable is set to anything, `is_err` will -return false and we will do a case insensitive search. We don't care about the -value that the environment variable is set to, just whether it's set or unset, -so we're checking `is_err` rather than `unwrap`, `expect`, or any of the other -methods we've seen on `Result`. We pass the value in the `case_sensitive` -variable to the `Config` instance so that the `run` function can read that -value and decide whether to call `search` or `search_case_insensitive` as we -implemented in Listing 12-22. +call the `env::var` function and pass it the name of the `CASE_INSENSITIVE` +environment variable. The `env::var` method returns a `Result` that will be the +successful `Ok` variant that contains the value of the environment variable if +the environment variable is set. It will return the `Err` variant if the +environment variable is not set. -Let's give it a try! First, we'll run our program without the environment -variable set and with the query "to", which should match any line that contains -the word "to" in all lowercase: +We’re using the `is_err` method on the `Result` to check to see if it’s an +error, and therefore unset, which means it *should* do a case sensitive search. +If the `CASE_INSENSITIVE` environment variable is set to anything, `is_err` +will return false and it will perform a case insensitive search. We don’t care +about the *value* of the environment variable, just whether it’s set or unset, +so we’re checking `is_err` rather than `unwrap`, `expect`, or any of the other +methods we’ve seen on `Result`. + +We pass the value in the `case_sensitive` variable to the `Config` instance so +that the `run` function can read that value and decide whether to call `search` +or `search_case_insensitive` as we implemented in Listing 12-22. + +Let’s give it a try! First, we’ll run our program without the environment +variable set and with the query “to”, which should match any line that contains +the word “to” in all lowercase: ``` $ cargo run to poem.txt @@ -1565,9 +1514,9 @@ Are you nobody, too? How dreary to be somebody! ``` -Looks like that still works! Now, let's run the program with `CASE_INSENSITIVE` -set to 1 but with the same query "to", and we should get lines that contain -"to" that might have capital letters: +Looks like that still works! Now, let’s run the program with `CASE_INSENSITIVE` +set to 1 but with the same query “to”, and we should get lines that contain +“to” that might have uppercase letters: ``` $ CASE_INSENSITIVE=1 cargo run to poem.txt @@ -1592,29 +1541,35 @@ environment variable, and decide which should take precedence if the program is run with contradictory values. The `std::env` module contains many more useful features for dealing with -environment variables; check out its documentation to see what's available. +environment variables; check out its documentation to see what’s available. -## Write to `stderr` Instead of `stdout` +## Writing Error Messages 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. +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. -We can verify that, the way we've written `minigrep` 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: +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 @@ -1629,75 +1584,55 @@ file. Let’s see what *output.txt* contains: Problem parsing arguments: not enough arguments ``` -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`: +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 ``` -extern crate minigrep; - -use std::env; -use std::process; -use std::io::prelude::*; - -use minigrep::Config; - fn main() { let args: Vec = 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"); + eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); if let Err(e) = minigrep::run(config) { - writeln!( - &mut stderr, - "Application error: {}", - e - ).expect("Could not write to stderr"); + eprintln!("Application error: {}", e); process::exit(1); } } ``` -Listing 12-23: Writing error messages to `stderr` instead of `stdout` using -`writeln!` +Listing 12-24: Writing error messages to `stderr` instead of `stdout` using +`eprintln!` -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 `>`: +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 that's expected of command line programs. +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 -redirecting standard out to a file: +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 @@ -1713,18 +1648,19 @@ 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. +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 `writeln!` macro with -`stderr`, you're now prepared to write command line applications. By using the +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. + diff --git a/second-edition/src/SUMMARY.md b/second-edition/src/SUMMARY.md index e55a67022..0880bab93 100644 --- a/second-edition/src/SUMMARY.md +++ b/second-edition/src/SUMMARY.md @@ -57,13 +57,13 @@ - [Running tests](ch11-02-running-tests.md) - [Test Organization](ch11-03-test-organization.md) -- [An I/O Project](ch12-00-an-io-project.md) +- [An I/O Project: Building a Command Line Program](ch12-00-an-io-project.md) - [Accepting Command Line Arguments](ch12-01-accepting-command-line-arguments.md) - [Reading a File](ch12-02-reading-a-file.md) - - [Improving Error Handling and Modularity](ch12-03-improving-error-handling-and-modularity.md) + - [Refactoring to Improve Modularity and Error Handling](ch12-03-improving-error-handling-and-modularity.md) - [Testing the Library's Functionality](ch12-04-testing-the-librarys-functionality.md) - [Working with Environment Variables](ch12-05-working-with-environment-variables.md) - - [Writing to `stderr` instead of `stdout`](ch12-06-writing-to-stderr-instead-of-stdout.md) + - [Writing Error Messages to `stderr` Instead of `stdout`](ch12-06-writing-to-stderr-instead-of-stdout.md) ## Thinking in Rust diff --git a/second-edition/src/ch12-00-an-io-project.md b/second-edition/src/ch12-00-an-io-project.md index 85c17bc5f..0bc309216 100644 --- a/second-edition/src/ch12-00-an-io-project.md +++ b/second-edition/src/ch12-00-an-io-project.md @@ -1,4 +1,4 @@ -# An I/O Project Building a Small Grep +# An I/O Project: Building a Command Line Program This chapter is both a recap of the many skills you’ve learned so far and an exploration of a few more standard library features. We’re going to build a @@ -8,24 +8,25 @@ practice some of the Rust you now have under your belt. Rust’s speed, safety, *single binary* output, and cross-platform support make it a good language for creating command line tools, so for our project we’ll make our own version of the classic command line tool `grep`. Grep is an -acronym for "Globally search a Regular Expression and Print." In the simplest -use case, `grep` searches a specified file for a specified string using the -following steps: +acronym for “Globally search a Regular Expression and Print.” In the simplest +use case, `grep` searches a specified file for a specified string. To do so, +`grep` takes a filename and a string as its arguments, then reads the file and +finds lines in that file that contain the string argument. It’ll then print out +those lines. -- Take as arguments a filename and a string. -- Read the file. -- Find lines in the file that contain the string argument. -- Print out those lines. - -We'll also show how to use environment variables and print to standard error -instead of standard out; these techniques are commonly used in command line -tools. +Along the way, we’ll show how to make our command line tool use features of the +terminal that many command line tools use. We'll read the value of an +environment variable in order to allow the user to configure the behavior of +our tool. We'll print to the standard error console stream (`stderr`) instead +of standard output (`stdout`) so that, for example, the user can choose to +redirect successful output to a file while still seeing error messages on the +screen. One Rust community member, Andrew Gallant, has already created a fully-featured, very fast version of `grep`, called `ripgrep`. By comparison, -our version of `grep` will be fairly simple, this chapter will give you some of -the background knowledge to help you understand a real-world project like -`ripgrep`. +our version of `grep` will be fairly simple, but this chapter will give you +some of the background knowledge to help you understand a real-world project +like `ripgrep`. This project will bring together a number of concepts you’ve learned so far: diff --git a/second-edition/src/ch12-01-accepting-command-line-arguments.md b/second-edition/src/ch12-01-accepting-command-line-arguments.md index 1e23a66a2..05fa16bb9 100644 --- a/second-edition/src/ch12-01-accepting-command-line-arguments.md +++ b/second-edition/src/ch12-01-accepting-command-line-arguments.md @@ -9,26 +9,26 @@ file to search in, like so: $ cargo run searchstring example-filename.txt ``` -Right now, the program generated by `cargo new` ignores any arguments we give -it. There are some existing libraries on crates.io that can help us accept -command line arguments, but since we're learning, let's implement this +Right now, the program generated by `cargo new` cannot process arguments we +give it. There are some existing libraries on crates.io that can help us accept +command line arguments, but since you’re learning, let’s implement this ourselves. ### Reading the Argument Values -In order to be able to get the values of command line arguments passed to our -program, we'll need to call a function provided in Rust's standard library: -`std::env::args`. This function returns an *iterator* of the command line -arguments that were given to our program. We haven't discussed iterators yet, -and we'll cover them fully in Chapter 13, but for our purposes now we only need -to know two things about iterators: +We first need to make sure our program is able to get the values of command +line arguments we pass to it, for which we’ll need a function provided in +Rust’s standard library: `std::env::args`. This function returns an *iterator* +of the command line arguments that were given to our program. We haven’t +discussed iterators yet, and we’ll cover them fully in Chapter 13, but for our +purposes now we only need to know two things about iterators: Iterators produce +a series of values, and we can call the `collect` function on an iterator to +turn it into a collection, such as a vector, containing all of the elements the +iterator produces. -1. Iterators produce a series of values. -2. We can call the `collect` function on an iterator to turn it into a vector - containing all of the elements the iterator produces. - -Let's give it a try: use the code in Listing 12-1 to read any command line -arguments passed to our `minigrep` program and collect them into a vector. +Let’s give it a try: use the code in Listing 12-1 to allow your `minigrep` +program to read any command line arguments passed it and then collect the +values into a vector. Filename: src/main.rs @@ -52,24 +52,26 @@ desired function is nested in more than one module, it’s conventional to bring the parent module into scope, rather than the function itself. This lets us easily use other functions from `std::env`. It’s also less ambiguous than adding `use std::env::args;` then calling the function with just `args`; that -might look like a function that's defined in the current module. +might easily be mistaken for a function that’s defined in the current module. -> Note: `std::env::args` will panic if any argument contains invalid Unicode. -> If you need to accept arguments containing invalid Unicode, use +> ### The `args` Function and Invalid Unicode +> +> Note that `std::env::args` will panic if any argument contains invalid +> Unicode. If you need to accept arguments containing invalid Unicode, use > `std::env::args_os` instead. That function returns `OsString` values instead > of `String` values. We’ve chosen to use `std::env::args` here for simplicity > because `OsString` values differ per-platform and are more complex to work > with than `String` values. On the first line of `main`, we call `env::args`, and immediately use `collect` -to turn the iterator into a vector containing all of the iterator's values. The -`collect` function can be used to create many kinds of collections, so we -explicitly annotate the type of `args` to specify that we want a vector of -strings. Though we very rarely need to annotate types in Rust, `collect` is one -function you do often need to annotate because Rust isn't able to infer what -kind of collection you want. +to turn the iterator into a vector containing all of the values produced by the +iterator. The `collect` function can be used to create many kinds of +collections, so we explicitly annotate the type of `args` to specify that we +want a vector of strings. Though we very rarely need to annotate types in Rust, +`collect` is one function you do often need to annotate because Rust isn’t able +to infer what kind of collection you want. -Finally, we print out the vector with the debug formatter, `:?`. Let's try +Finally, we print out the vector with the debug formatter, `:?`. Let’s try running our code with no arguments, and then with two arguments: ```text @@ -82,17 +84,20 @@ $ cargo run needle haystack ``` You may notice that the first value in the vector is `"target/debug/minigrep"`, -which is the name of our binary. The reasons for this are out of the scope of -this chapter, but we'll need to remember this as we save the two arguments we -need. +which is the name of our binary. This matches the behavior of the arguments +list in C, and lets programs use the name by which they were invoked in their +execution. It's convenient to have access to the program name in case we want +to print it in messages or change behavior of the program based on what command +line alias was used to invoke the program, but for the purposes of this chapter +we're going to ignore it and only save the two arguments we need. ### Saving the Argument Values in Variables -Printing out the value of the vector of arguments just illustrated that we're -able to access the values specified as command line arguments from our program. -That's not what we actually want to do, though, we want to save the values of -the two arguments in variables so that we can use the values in our program. -Let's do that as shown in Listing 12-2: +Printing out the value of the vector of arguments has illustrated that the +program is able to access the values specified as command line arguments. +That’s not actually our end goal, though: we want to save the values of the two +arguments in variables so that we can use the values in our program. Let’s do +that as shown in Listing 12-2: Filename: src/main.rs @@ -112,9 +117,9 @@ fn main() { Listing 12-2: Create variables to hold the query argument and filename argument -As we saw when we printed out the vector, the program's name takes up the first -value in the vector at `args[0]`, so we're starting at index `1`. The first -argument `minigrep` takes is the string we're searching for, so we put a +As we saw when we printed out the vector, the program’s name takes up the first +value in the vector at `args[0]`, so that we’re starting at index `1`. The +first argument `minigrep` takes is the string we’re searching for, so we put a reference to the first argument in the variable `query`. The second argument will be the filename, so we put a reference to the second argument in the variable `filename`. @@ -131,7 +136,8 @@ Searching for test In file sample.txt ``` -Great, it's working! We're saving the values of the arguments that we need into -the right variables. Later we'll add some error handling to deal with -situations such as when the user provides no arguments, but for now we'll -ignore that and work on adding file reading capabilities instead. +Great, it’s working! The values of the arguments we need are being saved into +the right variables. Later we’ll add some error handling to deal with certain +potential erroneous situations, such as when the user provides no arguments, +but for now we’ll ignore that and work on adding file reading capabilities +instead. diff --git a/second-edition/src/ch12-02-reading-a-file.md b/second-edition/src/ch12-02-reading-a-file.md index 82fbaa57a..1d21ba099 100644 --- a/second-edition/src/ch12-02-reading-a-file.md +++ b/second-edition/src/ch12-02-reading-a-file.md @@ -1,11 +1,12 @@ ## Reading a File -Next, we're going to read the file that we specify in the filename command line -argument. First, we need a sample file to test it with---the best kind of file -to use to make sure that `minigrep` is working is one with a small amount of text -over multiple lines with some repeated words. Listing 12-3 has an Emily -Dickinson poem that will work well! Create a file called `poem.txt` at the root -level of your project, and enter the poem "I'm nobody! Who are you?": +Next, we’re going to add functionality to read the file that specified in the +`filename` command line argument. First, we need a sample file to test it +with—the best kind of file to use to make sure that `minigrep` is working is +one with a small amount of text over multiple lines with some repeated words. +Listing 12-3 has an Emily Dickinson poem that will work well! Create a file +called `poem.txt` at the root level of your project, and enter the poem “I’m +nobody! Who are you?”: Filename: poem.txt @@ -35,18 +36,20 @@ use std::fs::File; use std::io::prelude::*; fn main() { - let args: Vec = env::args().collect(); - - let query = &args[1]; - let filename = &args[2]; - - println!("Searching for {}", query); +# let args: Vec = env::args().collect(); +# +# let query = &args[1]; +# let filename = &args[2]; +# +# println!("Searching for {}", query); + // ...snip... println!("In file {}", filename); let mut f = File::open(filename).expect("file not found"); let mut contents = String::new(); - f.read_to_string(&mut contents).expect("something went wrong reading the file"); + f.read_to_string(&mut contents) + .expect("something went wrong reading the file"); println!("With text:\n{}", contents); } @@ -60,8 +63,8 @@ standard library: we need `std::fs::File` for dealing with files, and `std::io::prelude::*` contains various traits that are useful when doing I/O, including file I/O. In the same way that Rust has a general prelude that brings certain things into scope automatically, the `std::io` module has its own -prelude of common things you'll need when working with I/O. Unlike the default -prelude, we must explicitly `use` the prelude in `std::io`. +prelude of common things you’ll need when working with I/O. Unlike the default +prelude, we must explicitly `use` the prelude from `std::io`. In `main`, we’ve added three things: first, we get a mutable handle to the file by calling the `File::open` function and passing it the value of the `filename` @@ -70,9 +73,9 @@ mutable, empty `String`. This will hold the content of the file after we read it in. Third, we call `read_to_string` on our file handle and pass a mutable reference to `contents` as an argument. -After those lines, we've again added temporary `println!` that prints out the -value in `contents` after we've read the file so we can check that our program -is working so far. +After those lines, we’ve again added a temporary `println!` statement that +prints out the value of `contents` after the file is read, so that we can check +that our program is working so far. Let’s try running this code with any string as the first command line argument (since we haven’t implemented the searching part yet) and our *poem.txt* file @@ -96,10 +99,11 @@ To tell your name the livelong day To an admiring bog! ``` -Great! Our code read in and printed out the content of the file. We've got a -few flaws though: the `main` function has multiple responsibilities, and we're -not handling errors as well as we could be. While our program is still small, -these flaws aren't a big problem, but as our program grows, it will be harder -to fix them cleanly. It's good practice to begin refactoring early on when -developing a program, as it's much easier to refactor smaller amounts of code, -so we'll do that now. +Great! Our code read in and printed out the content of the file. We’ve got a +few flaws though. The `main` function has multiple responsibilities; generally +functions are clearer and easier to maintain if each function is responsible +for only one idea. The other problem is that we’re not handling errors as well +as we could be. While our program is still small, these flaws aren’t a big +problem, but as our program grows, it will be harder to fix them cleanly. It’s +good practice to begin refactoring early on when developing a program, as it’s +much easier to refactor smaller amounts of code, so we’ll do that now. diff --git a/second-edition/src/ch12-03-improving-error-handling-and-modularity.md b/second-edition/src/ch12-03-improving-error-handling-and-modularity.md index 28fb2a33a..1d3979697 100644 --- a/second-edition/src/ch12-03-improving-error-handling-and-modularity.md +++ b/second-edition/src/ch12-03-improving-error-handling-and-modularity.md @@ -19,12 +19,12 @@ we’re going to need to bring into scope; the more variables we have in scope, the harder it is to keep track of the purpose of each. It’s better to group the configuration variables into one structure to make their purpose clear. -The third problem is that we've used `expect` to print out an error message if -opening the file fails, but the error message only says `file not found`. There -are a number of ways that opening a file can fail besides a missing file: for -example, the file might exist, but we might not have permission to open it. -Right now, if we're in that situation, we'd print the `file not found` error -message that would give the user the wrong advice! +The third problem is that we’ve used `expect` to print out an error message +when opening the file fails, but the error message only says `file not found`. +There are a number of ways that opening a file can fail besides the file being +missing: for example, the file might exist, but we might not have permission to +open it. Right now, if we’re in that situation, we’d print the `file not found` +error message that would give the user the wrong advice! Fourth, we use `expect` repeatedly to deal with different errors, and if the user runs our programs without specifying enough arguments, they’ll get an @@ -39,23 +39,23 @@ Let’s address these problems by refactoring our project. ### Separation of Concerns for Binary Projects -The organizational problem of having the `main` function responsible for -multiple tasks is common to many binary projects, so the Rust community has -developed a kind of guideline process for splitting up the separate concerns of -a binary program when `main` starts getting large. The process has the -following steps: +The organizational problem of allocating responsibility for multiple tasks to +the `main` function responsible is common to many binary projects, so the Rust +community has developed a kind of guideline process for splitting up the +separate concerns of a binary program when `main` starts getting large. The +process has the following steps: -1. Split your program into both a *main.rs* and a *lib.rs* and move your - program's logic into *lib.rs*. -2. While your command line parsing logic is small, it can remain in *main.rs*. -3. When the command line parsing logic starts getting complicated, extract it - from *main.rs* into *lib.rs* as well. -4. The responsibilities that remain in the `main` function after this process - should be: - * Calling the command line parsing logic with the argument values - * Setting up any other configuration - * Calling a `run` function in *lib.rs* - * If `run` returns an error, handling that error +* Split your program into both a *main.rs* and a *lib.rs* and move your +program’s logic into *lib.rs*. +* While your command line parsing logic is small, it can remain in *main.rs*. +* When the command line parsing logic starts getting complicated, extract it +from *main.rs* into *lib.rs* as well. +* The responsibilities that remain in the `main` function after this process +should be limited to: + * Calling the command line parsing logic with the argument values + * Setting up any other configuration + * Calling a `run` function in *lib.rs* + * If `run` returns an error, handling that error This pattern is all about separating concerns: *main.rs* handles running the program, and *lib.rs* handles all of the logic of the task at hand. Because we @@ -64,7 +64,7 @@ program’s logic by moving it into functions in *lib.rs*. The only code that remains in *main.rs* will be small enough to verify its correctness by reading it. Let’s re-work our program by following this process. -### Extracting the Argument Parser +#### Extracting the Argument Parser First, we’ll extract the functionality for parsing arguments. Listing 12-5 shows the new start of `main` that calls a new function `parse_config`, which @@ -96,10 +96,10 @@ We’re still collecting the command line arguments into a vector, but instead o assigning the argument value at index `1` to the variable `query` and the argument value at index `2` to the variable `filename` within the `main` function, we pass the whole vector to the `parse_config` function. The -`parse_config` function then holds the logic that knows which argument goes in -which variable, and passes the values back to `main`. We still create the -`query` and `filename` variables in `main`, but `main` no longer has the -responsibility of knowing how the command line arguments and variables +`parse_config` function then holds the logic that determines which argument +goes in which variable, and passes the values back to `main`. We still create +the `query` and `filename` variables in `main`, but `main` no longer has the +responsibility of determining how the command line arguments and variables correspond. This may seem like overkill for our small program, but we’re refactoring in @@ -183,7 +183,7 @@ since we don’t have to manage the lifetimes of the references, so in this circumstance giving up a little performance to gain simplicity is a worthwhile trade-off. -> #### The Tradeoffs of Using `clone` +> ### The Tradeoffs of Using `clone` > > There’s a tendency among many Rustaceans to avoid using `clone` to fix > ownership problems because of its runtime cost. In Chapter 13 on iterators, @@ -195,15 +195,15 @@ trade-off. > Rust, it’ll be easier to go straight to the desirable method, but for now it’s > perfectly acceptable to call `clone`. -We've updated `main` so that it places the instance of `Config` that -`parse_config` returns into a variable named `config`, and updated the code -that previously used the separate `query` and `filename` variables so that is -now uses the fields on the `Config` struct instead. +We’ve updated `main` so that it places the instance of `Config` returned by +`parse_config` into a variable named `config`, and updated the code that +previously used the separate `query` and `filename` variables so that it now +uses the fields on the `Config` struct instead. -Our code now more clearly conveys our intent that `query` and `filename` are -related and their purpose is to configure how the program will work. Any code -that uses these values knows to find them in the `config` instance in the -fields named for their purpose. +Our code now more clearly conveys that `query` and `filename` are related and +their purpose is to configure how the program will work. Any code that uses +these values knows to find them in the `config` instance in the fields named +for their purpose. #### Creating a Constructor for `Config` @@ -219,9 +219,9 @@ instance, we can change `parse_config` from being a plain function into a function named `new` that is associated with the `Config` struct. Making this change will make our code more idiomatic: we can create instances of types in the standard library like `String` by calling `String::new`, and by changing -`parse_config` to be a `new` function associated with `Config`, we'll be able -to create instances of `Config` by calling `Config::new`. Listing 12-7 shows -the changes we'll need to make: +`parse_config` into a `new` function associated with `Config`, we’ll be able to +create instances of `Config` by calling `Config::new`. Listing 12-7 shows the +changes we’ll need to make: Filename: src/main.rs @@ -277,16 +277,16 @@ but the index is 1', /stable-dist-rustc/build/src/libcollections/vec.rs:1307 note: Run with `RUST_BACKTRACE=1` for a backtrace. ``` -`index out of bounds: the len is 1 but the index is 1` is an error message that -is intended for programmers, and won't really help our end users understand -what happened and what they should do instead. Let's fix that now. +The line that states `index out of bounds: the len is 1 but the index is 1` is +an error message intended for programmers, and won’t really help our end users +understand what happened and what they should do instead. Let’s fix that now. #### Improving the Error Message -In Listing 12-8, we're adding a check in the `new` function to check that the -slice is long enough before accessing index 1 and 2. If the slice isn't long -enough, we panic with a better error message than the `index out of bounds` -message: +In Listing 12-8, we’re adding a check in the `new` function that will check +that the slice is long enough before accessing index `1` and `2`. If the slice +isn’t long enough, the program panics, with a better error message than the +`index out of bounds` message: Filename: src/main.rs @@ -302,13 +302,13 @@ fn new(args: &[String]) -> Config { Listing 12-8: Adding a check for the number of arguments -This is similar to the `Guess::new` function we wrote in Listing 9-8, where we -called `panic!` if the `value` argument was out of the range of valid values. -Instead of checking for a range of values, we're checking that the length of -`args` is at least 3, and the rest of the function can operate under the -assumption that this condition has been met. If `args` has fewer than 3 items, -this condition will be true, and we call the `panic!` macro to end the program -immediately. +This is similar to the `Guess::new` function we wrote in Listing 9-8, where +`panic!` was called when the `value` argument was out of the range of valid +values. Instead of checking for a range of values here, we’re checking that the +length of `args` is at least 3, and the rest of the function can operate under +the assumption that this condition has been met. If `args` has fewer than 3 +items, this condition will be true, and we call the `panic!` macro to end the +program immediately. With these extra few lines of code in `new`, let’s try running our program without any arguments again and see what the error looks like now: @@ -325,22 +325,22 @@ This output is better, we now have a reasonable error message. However, we also have a bunch of extra information we don’t want to give to our users. So perhaps using the technique we used in Listing 9-8 isn’t the best to use here; a call to `panic!` is more appropriate for a programming problem rather than a -usage problem anyway, as we discussed in Chapter 9. Instead, we can use the -other technique we learned about in that chapter: returning a `Result` that can +usage problem, as we discussed in Chapter 9. Instead, we can use the other +technique you also learned about in Chapter 9: returning a `Result` that can indicate either success or an error. #### Returning a `Result` from `new` Instead of Calling `panic!` We can choose to instead return a `Result` value that will contain a `Config` instance in the successful case, and will describe the problem in the error -case. When `Config::new` is communicating to `main`, we can use Rust's way of -signaling that there was a problem using the `Result` type. Then we can change -`main` to convert an `Err` variant into a nicer error for our users, without -the surrounding text about `thread 'main'` and `RUST_BACKTRACE` that a call to +case. When `Config::new` is communicating to `main`, we can use the `Result` +type to signal that there was a problem. Then we can change `main` to convert +an `Err` variant into a more practical error for our users, without the +surrounding text about `thread 'main'` and `RUST_BACKTRACE` that a call to `panic!` causes. -Listing 12-9 shows the changes to the return value of `Config::new` and the -body of the function needed to return a `Result`: +Listing 12-9 shows the changes you need to make to the return value of +`Config::new` and the body of the function needed to return a `Result`: Filename: src/main.rs @@ -378,11 +378,11 @@ more cleanly in the error case. #### Calling `Config::new` and Handling Errors In order to handle the error case and print a user-friendly message, we need to -update `main` to handle the `Result` that `Config::new` is now returning as -shown in Listing 12-10. We're also going to implement by hand something that -`panic!` handled for us: exiting the command line tool with an error code of 1. -A nonzero exit status is a convention to signal to the process that called our -program that our program ended with an error state. +update `main` to handle the `Result` being returned by `Config::new`, as shown +in Listing 12-10. We’re also going to take the responsibility of exiting the +command line tool with a nonzero error code from `panic!` and implement it by +hand. A nonzero exit status is a convention to signal to the process that +called our program that our program ended with an error state. Filename: src/main.rs @@ -418,12 +418,11 @@ pipes. The code in the closure can then use the `err` value when it runs. We’ve added a new `use` line to import `process` from the standard library. The code in the closure that will get run in the error case is only two lines: we -print out the `err` value, then call `std::process::exit` (we've added a new -`use` line at the top to import `process` from the standard library). -`process::exit` will stop the program immediately and return the number that -was passed as the exit status code. This is similar to the `panic!`-based -handling we used in Listing 12-8, with the exception that we no longer get all -the extra output. Let's try it: +print out the `err` value, then call `process::exit`. The `process::exit` +function will stop the program immediately and return the number that was +passed as the exit status code. This is similar to the `panic!`-based handling +we used in Listing 12-8, with the exception that we no longer get all the extra +output. Let’s try it: ```text $ cargo run @@ -435,18 +434,18 @@ Problem parsing arguments: not enough arguments Great! This output is much friendlier for our users. -### Extracting a `run` Function +### Extracting Logic from `main` -Now we're done refactoring our configuration parsing; let's turn to our -program's logic. As we laid out in the process we discussed in the "Separation -of Concerns for Binary Projects" section, we're going to extract a function -named `run` that will hold all of the logic currently in the `main` function -that isn't setting up configuration or handling errors. Once we're done, `main` -will be concise and easy to verify by inspection, and we'll be able to write -tests for all of the other logic. +Now we’re done refactoring our configuration parsing; let’s turn to our +program’s logic. As we laid out in the “Separation of Concerns for Binary +Projects” section, we’re going to extract a function named `run` that will hold +all of the logic currently in the `main` function not involved with setting up +configuration or handling errors. Once we’re done, `main` will be concise and +easy to verify by inspection, and we’ll be able to write tests for all of the +other logic. -Listing 12-11 shows the extracted `run` function. For now, we're making only -the small, incremental improvement of extracting the function and still +Listing 12-11 shows the extracted `run` function. For now, we’re making only +the small, incremental improvement of extracting the function. We’re still defining the function in *src/main.rs*: Filename: src/main.rs @@ -483,13 +482,13 @@ argument. #### Returning Errors from the `run` Function -With the remaining program logic separated into the `run` function rather than -being in `main`, we can improve the error handling like we did with -`Config::new` in Listing 12-9. Instead of allowing the program to panic by -calling `expect`, the `run` function will return a `Result` when -something goes wrong. This will let us further consolidate the logic around -handling errors in a user-friendly way into `main`. Listing 12-12 shows the -changes to the signature and body of `run`: +With the remaining program logic separated into the `run` function, we can +improve the error handling like we did with `Config::new` in Listing 12-9. +Instead of allowing the program to panic by calling `expect`, the `run` +function will return a `Result` when something goes wrong. This will let +us further consolidate the logic around handling errors in a user-friendly way +into `main`. Listing 12-12 shows the changes you need to make to the signature +and body of `run`: Filename: src/main.rs @@ -554,8 +553,8 @@ to have some error handling code here! Let’s rectify that now. #### Handling Errors Returned from `run` in `main` -We'll check for errors and handle them nicely using a similar technique to the -way we handled errors with `Config::new` in Listing 12-10, but with a slight +We’ll check for errors and handle them using a similar technique to the way we +handled errors with `Config::new` in Listing 12-10, but with a slight difference: Filename: src/main.rs @@ -585,21 +584,22 @@ value as it would only be `()`. The bodies of the `if let` and the `unwrap_or_else` functions are the same in both cases though: we print out the error and exit. -### Split Code into a Library Crate +### Splitting Code into a Library Crate This is looking pretty good so far! Now we’re going to split the *src/main.rs* file up and put some code into *src/lib.rs* so that we can test it and have a -small `main` function. +*src/main.rs* file with fewer responsibilities. -Let's move the following pieces of code from *src/main.rs* to a new file, -*src/lib.rs*: +Let’s move everything that isn't the `main` function from *src/main.rs* to a +new file, *src/lib.rs*: * The `run` function definition * The relevant `use` statements * The definition of `Config` * The `Config::new` function definition -The contents of *src/lib.rs* should now look like Listing 12-13: +The contents of *src/lib.rs* should have the signatures shown in Listing 12-13 +(we've omitted the bodies of the functions for brevity): Filename: src/lib.rs @@ -615,26 +615,12 @@ pub struct Config { impl Config { pub fn new(args: &[String]) -> Result { - if args.len() < 3 { - return Err("not enough arguments"); - } - - let query = args[1].clone(); - let filename = args[2].clone(); - - Ok(Config { query, filename }) + // ...snip... } } -pub fn run(config: Config) -> Result<(), Box>{ - let mut f = File::open(config.filename)?; - - let mut contents = String::new(); - f.read_to_string(&mut contents)?; - - println!("With text:\n{}", contents); - - Ok(()) +pub fn run(config: Config) -> Result<(), Box> { + // ...snip... } ``` @@ -643,14 +629,12 @@ pub fn run(config: Config) -> Result<(), Box>{ We’ve made liberal use of `pub` here: on `Config`, its fields and its `new` method, and on the `run` function. We now have a library crate that has a -public API that we can test. - -#### Calling the Library Crate from the Binary Crate +public API that we can test! Now we need to bring the code we moved to *src/lib.rs* into the scope of the -binary crate in *src/main.rs* by using `extern crate minigrep`. Then we’ll add a -`use minigrep::Config` line to bring the `Config` type into scope, and prefix the -`run` function with our crate name as shown in Listing 12-14: +binary crate in *src/main.rs* by using `extern crate minigrep`. Then we’ll add +a `use minigrep::Config` line to bring the `Config` type into scope, and prefix +the `run` function with our crate name as shown in Listing 12-14: Filename: src/main.rs @@ -663,20 +647,9 @@ use std::process; use minigrep::Config; fn main() { - let args: Vec = env::args().collect(); - - let config = Config::new(&args).unwrap_or_else(|err| { - println!("Problem parsing arguments: {}", err); - process::exit(1); - }); - - println!("Searching for {}", config.query); - println!("In file {}", config.filename); - + // ...snip... if let Err(e) = minigrep::run(config) { - println!("Application error: {}", e); - - process::exit(1); + // ...snip... } } ``` @@ -684,8 +657,11 @@ fn main() { Listing 12-14: Bringing the `minigrep` crate into the scope of *src/main.rs* -With that, all the functionality should be connected and should work. Give it a -`cargo run` and make sure everything is wired up correctly. +To bring the library crate into the binary crate, we use `extern crate` +`minigrep`. Then we’ll add a `use` `minigrep``::Config` line to bring the +`Config` type into scope, and we'll prefix the `run` function with our crate +name. With that, all the functionality should be connected and should work. +Give it a `cargo run` and make sure everything is wired up correctly. Whew! That was a lot of work, but we’ve set ourselves up for success in the future. Now it’s much easier to handle errors, and we’ve made our code more diff --git a/second-edition/src/ch12-04-testing-the-librarys-functionality.md b/second-edition/src/ch12-04-testing-the-librarys-functionality.md index 5bdeedd3b..81cadc00d 100644 --- a/second-edition/src/ch12-04-testing-the-librarys-functionality.md +++ b/second-edition/src/ch12-04-testing-the-librarys-functionality.md @@ -10,21 +10,21 @@ In this section, we’re going to follow the Test Driven Development (TDD) process. This is a software development technique that follows this set of steps: -1. Write a test that fails, and run it to make sure it fails for the reason - you expected. -2. Write or modify just enough code to make the new test pass. -3. Refactor the code you just added or changed, and make sure the tests - continue to pass. -4. Repeat! +* Write a test that fails, and run it to make sure it fails for the reason you +expected. +* Write or modify just enough code to make the new test pass. +* Refactor the code you just added or changed, and make sure the tests continue +to pass. +* Repeat! This is just one of many ways to write software, but TDD can help drive the -design of code. Writing the test before writing the code that makes the test +design of code. Writing the test before you write the code that makes the test pass helps to maintain high test coverage throughout the process. -We're going to test drive the implementation of the part of our `minigrep` -program that will actually do the searching for the query string in the file -contents and produce a list of lines that match the query. We're going to add -this functionality in a function called `search`. +We’re going to test drive the implementation of the functionality that will +actually do the searching for the query string in the file contents and produce +a list of lines that match the query. We’re going to add this functionality in +a function called `search`. ### Writing a Failing Test @@ -65,23 +65,22 @@ Pick three."; Listing 12-15: Creating a failing test for the `search` function we wish we had -We've chosen to use "duct" as the string we're looking for in this test. The -text we're searching in is three lines, only one of which contains "duct". We -assert that the value returned from the `search` function contains only the one -line we expect. +The string we are searching for is “duct” in this test. The text we’re +searching is three lines, only one of which contains “duct”. We assert that +the value returned from the `search` function contains only the line we expect. -We aren't able to run this test and watch it fail though, since this test -doesn't even compile yet! We're going to add just enough code to get it to -compile: a definition of the `search` function that always returns an empty -vector, as shown in Listing 12-16. Once we have this, the test should compile -and fail because an empty vector doesn't match a vector containing the one -line `"safe, fast, productive."`. +We aren’t able to run this test and watch it fail though, since this test +doesn’t even compile–the search function doesn't exist yet! So now we’ll add +just enough code to get the tests to compile and run: a definition of the +`search` function that always returns an empty vector, as shown in Listing +12-16. Once we have this, the test should compile and fail because an empty +vector doesn’t match a vector containing the line `"safe, fast, productive."`. Filename: src/lib.rs -```rust -fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { - vec![] +``` +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { + vec![] } ``` @@ -154,16 +153,16 @@ error: test failed Great, our test fails, exactly as we expected. Let’s get the test to pass! -### Writing Code that Gets the Test to Pass +### Writing Code to Pass the Test Currently, our test is failing because we always return an empty vector. To fix that and implement `search`, our program needs to follow these steps: -1. Iterate through each line of the contents. -2. Check if the line contains our query string. - * If it does, add it to the list of values we're returning. - * If it doesn't, do nothing. -3. Return the list of results that match. +* Iterate through each line of the contents. +* Check if the line contains our query string. +* If it does, add it to the list of values we’re returning. +* If it doesn’t, do nothing. +* Return the list of results that match. Let’s take each step at a time, starting with iterating through lines. @@ -175,7 +174,7 @@ conveniently named `lines`, that works as shown in Listing 12-17: Filename: src/lib.rs ```rust,ignore -fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { for line in contents.lines() { // do something with line } @@ -200,7 +199,7 @@ Listing 12-18: Filename: src/lib.rs ```rust,ignore -fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { for line in contents.lines() { if line.contains(query) { // do something with line @@ -222,7 +221,7 @@ vector, as shown in Listing 12-19: Filename: src/lib.rs ```rust,ignore -fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { +pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { @@ -247,28 +246,16 @@ running 1 test test test::one_result ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured - - Running target/debug/minigrep-2f55ee8cd1721808 - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured - - Doc-tests minigrep - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured ``` Our test passed, great, it works! Now that our test is passing, we could consider opportunities for refactoring -the implementation of the `search` function while keeping the tests passing in -order to maintain the same functionality while we do so. This code isn't bad, -but it isn't taking advantage of some useful features of iterators. We'll be -coming back to this example in Chapter 13 where we'll explore iterators in -detail and see how to improve it. +the implementation of the `search` function while keeping the code that passes +the tests, in order to maintain the same functionality. The code in the +`search` function isn’t too bad, but it isn’t taking advantage of some useful +features of iterators. We’ll be coming back to this example in Chapter 13 where +we’ll explore iterators in detail and see how to improve it. #### Using the `search` Function in the `run` Function @@ -294,8 +281,8 @@ pub fn run(config: Config) -> Result<(), Box> { } ``` -We're again using a `for` loop to get each line returned from `search`, and -the code that we run for each line prints it out. +We’re still using a `for` loop to get each line returned from `search` and +printing out each line. Now our whole program should be working! Let’s try it out, first with a word that should return exactly one line from the Emily Dickinson poem, “frog”: @@ -327,11 +314,11 @@ $ cargo run monomorphization poem.txt Running `target/debug/minigrep monomorphization poem.txt` ``` -Excellent! We've built our own version of a classic tool, and learned a lot -about how to structure applications. We've also learned a bit about file input -and output, lifetimes, testing, and command line parsing. +Excellent! We’ve built our own mini version of a classic tool, and learned a +lot about how to structure applications. We’ve also learned a bit about file +input and output, lifetimes, testing, and command line parsing. -Feel free to move on to Chapter 13 if you'd like at this point. To round out -this project chapter, though, we're going to briefly demonstrate how to work -with environment variables and printing to standard error, both of which are -useful when writing command line programs. +To round out this project chapter, we’re going to briefly demonstrate how to +work with environment variables and how to print to standard error, both of +which are useful when writing command line programs. Feel free to move on to +Chapter 13 if you’d like at this point. diff --git a/second-edition/src/ch12-05-working-with-environment-variables.md b/second-edition/src/ch12-05-working-with-environment-variables.md index 50c2b81a9..12e5e3540 100644 --- a/second-edition/src/ch12-05-working-with-environment-variables.md +++ b/second-edition/src/ch12-05-working-with-environment-variables.md @@ -1,22 +1,22 @@ ## Working with Environment Variables -We're going to improve our tool with an extra feature: an option for case -insensitive searching turned on via an environment variable. We could make this -a command line option and require that users enter it each time they want it to -apply, but instead we're going to use an environment variable. This allows our -users to set the environment variable once and have all their searches be case -insensitive in that terminal session. +We’re going to improve our tool with an extra feature: an option for case +insensitive searching that the user can turn on via an environment variable. We +could make this a command line option and require that users enter it each time +they want it to apply, but instead we’re going to use an environment variable. +This allows our users to set the environment variable once and have all their +searches be case insensitive in that terminal session. ### Writing a Failing Test for the Case-Insensitive `search` Function -First, let's add a new function that we will call when the environment variable -is on. +We want to add a new `search_case_insensitive` function that we will call when +the environment variable is on. -We're going to continue following the TDD process that we started doing in the -last section, and the first step is again to write a failing test. We'll add a -new test for the new case insensitive search function, and rename our old test -from `one_result` to `case_sensitive` to be clearer about the differences -between the two tests, as shown in Listing 12-20: +We’re going to continue following the TDD process, so the first step is again +to write a failing test. We’ll add a new test for the new case-insensitive +search function, and rename our old test from `one_result` to `case_sensitive` +to be clearer about the differences between the two tests, as shown in Listing +12-20: Filename: src/lib.rs @@ -60,30 +60,29 @@ Trust me."; Listing 12-20: Adding a new failing test for the case insensitive function we’re about to add -Note that we've edited the old test's `contents` too. We've added a new line -with the text "Duct tape", with a capital D, that shouldn't match the query -"duct" when we're searching for the query in a case sensitive manner. We've -changed this test to ensure that we don't accidentally break the case sensitive -search functionality that we've already implemented; this test should pass now +Note that we’ve edited the old test’s `contents` too. We've added a new line +with the text “Duct tape”, with a capital D, that shouldn’t match the query +“duct” when we’re searching in a case sensitive manner. Changing the old test +in this way helps ensure that we don’t accidentally break the case sensitive +search functionality that we’ve already implemented; this test should pass now and should continue to pass as we work on the case insensitive search. -The new test for the case insensitive search uses "rUsT" with some capital -letters as its query. The expected return value from the -`search_case_insensitive` function we're going to add is that the query "rust" -will match both the line containing "Rust:" with a capital R and also the line -"Trust me." that contains "rust" with a lowercase r. This test will fail to -compile right now since we haven't yet defined the `search_case_insensitive` -function; feel free to add a skeleton implementation that always returns an -empty vector in the same way that we did for the `search` function in Listing -12-16 in order to see the test compile and fail. +The new test for the case *insensitive* search uses “rUsT” as its query. In the +`search_case_insensitive` function we’re going to add, the query “rUsT” should +match both the line containing “Rust:” with a capital R and also the line +“Trust me.” even though both of those have different casing than the query. +This is our failing test, and it will fail to compile because we haven’t yet +defined the `search_case_insensitive` function. Feel free to add a skeleton +implementation that always returns an empty vector in the same way that we did +for the `search` function in Listing 12-16 in order to see the test compile and +fail. ### Implementing the `search_case_insensitive` Function The `search_case_insensitive` function, shown in Listing 12-21, will be almost -the same as the `search` function. The difference is that we'll lowercase the -`query` function and each `line` so that whatever the case of the input -arguments, they will be the same case when we check whether the line contains -the query. +the same as the `search` function. The only difference is that we’ll lowercase +the `query` and each `line` so that whatever the case of the input arguments, +they will be the same case when we check whether the line contains the query. Filename: src/lib.rs @@ -111,42 +110,26 @@ matter if the user’s query is “rust”, “RUST”, “Rust”, or “rUsT the query as if it was “rust” and be insensitive to the case. Note that `query` is now a `String` rather than a string slice, because calling -`to_lowercase` is creating new data, not referencing existing data. If the -query is "rUsT", that string slice does not contain a lowercase u or t for us -to use, so we have to allocate a new `String` containing "rust". Because -`query` is now a `String`, when we pass `query` as an argument to the -`contains` method, we need to add an ampersand since the signature of -`contains` is defined to take a string slice. +`to_lowercase` creates new data rather than referencing existing data. Say the +query is “rUsT”, as an example: that string slice does not contain a lowercase +“u” or “t” for us to use, so we have to allocate a new `String` containing +“rust”. When we pass `query` as an argument to the `contains` method now, we +need to add an ampersand because the signature of `contains` is defined to take +a string slice. Next, we add a call to `to_lowercase` on each `line` before we check if it -contains `query`. This will turn "Rust:" into "rust:" and "Trust me." into -"trust me." Now that we've converted both `line` and `query` to all lowercase, -we'll find matches no matter what case the text in the file has or the user -entered in the query. +contains `query` to lowercase all characters. Now that we’ve converted both +`line` and `query` to lowercase, we’ll find matches no matter what the case of +the query. Let’s see if this implementation passes the tests: ```text - Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs - Running target/debug/deps/minigrep-e58e9b12d35dc861 - running 2 tests test test::case_insensitive ... ok test test::case_sensitive ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured - - Running target/debug/minigrep-8a7faa2662b5030a - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured - - Doc-tests minigrep - -running 0 tests - -test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured ``` Great! Now, let’s actually call the new `search_case_insensitive` function from @@ -215,10 +198,10 @@ pub fn run(config: Config) -> Result<(), Box>{ Finally, we need to actually check for the environment variable. The functions for working with environment variables are in the `env` module in the standard -library, so we want to bring that module into scope with a `use std::env;` -line at the top of *src/lib.rs*. Then we're going to use the `var` method -from the `env` module in `Config::new` to check for an environment variable -named `CASE_INSENSITIVE`, as shown in Listing 12-23: +library, so we want to bring that module into scope with a `use std::env;` line +at the top of *src/lib.rs*. Then we’re going to use the `var` method from the +`env` module to check for an environment variable named `CASE_INSENSITIVE`, as +shown in Listing 12-23: Filename: src/lib.rs @@ -243,11 +226,7 @@ impl Config { let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); - Ok(Config { - query: query, - filename: filename, - case_sensitive: case_sensitive, - }) + Ok(Config { query, filename, case_sensitive }) } } ``` @@ -256,24 +235,27 @@ impl Config { `CASE_INSENSITIVE` Here, we create a new variable `case_sensitive`. In order to set its value, we -call the `env::var` function and pass it the name of the environment variable -we're looking for, `CASE_INSENSITIVE`. `env::var` returns a `Result` that will -be the `Ok` variant containing the value if the environment variable is set, -and will be the `Err` variant if the environment variable is not set. We're -using the `is_err` method on the `Result` to check to see if it's an error (and -therefore unset), which means we *should* do a case sensitive search. If the -`CASE_INSENSITIVE` environment variable is set to anything, `is_err` will -return false and we will do a case insensitive search. We don't care about the -value that the environment variable is set to, just whether it's set or unset, -so we're checking `is_err` rather than `unwrap`, `expect`, or any of the other -methods we've seen on `Result`. We pass the value in the `case_sensitive` -variable to the `Config` instance so that the `run` function can read that -value and decide whether to call `search` or `search_case_insensitive` as we -implemented in Listing 12-22. +call the `env::var` function and pass it the name of the `CASE_INSENSITIVE` +environment variable. The `env::var` method returns a `Result` that will be the +successful `Ok` variant that contains the value of the environment variable if +the environment variable is set. It will return the `Err` variant if the +environment variable is not set. -Let's give it a try! First, we'll run our program without the environment -variable set and with the query "to", which should match any line that contains -the word "to" in all lowercase: +We’re using the `is_err` method on the `Result` to check to see if it’s an +error, and therefore unset, which means it *should* do a case sensitive search. +If the `CASE_INSENSITIVE` environment variable is set to anything, `is_err` +will return false and it will perform a case insensitive search. We don’t care +about the *value* of the environment variable, just whether it’s set or unset, +so we’re checking `is_err` rather than `unwrap`, `expect`, or any of the other +methods we’ve seen on `Result`. + +We pass the value in the `case_sensitive` variable to the `Config` instance so +that the `run` function can read that value and decide whether to call `search` +or `search_case_insensitive` as we implemented in Listing 12-22. + +Let’s give it a try! First, we’ll run our program without the environment +variable set and with the query “to”, which should match any line that contains +the word “to” in all lowercase: ```text $ cargo run to poem.txt @@ -283,9 +265,9 @@ Are you nobody, too? How dreary to be somebody! ``` -Looks like that still works! Now, let's run the program with `CASE_INSENSITIVE` -set to 1 but with the same query "to", and we should get lines that contain -"to" that might have capital letters: +Looks like that still works! Now, let’s run the program with `CASE_INSENSITIVE` +set to 1 but with the same query “to”, and we should get lines that contain +“to” that might have uppercase letters: ```text $ CASE_INSENSITIVE=1 cargo run to poem.txt @@ -310,4 +292,4 @@ environment variable, and decide which should take precedence if the program is run with contradictory values. The `std::env` module contains many more useful features for dealing with -environment variables; check out its documentation to see what's available. +environment variables; check out its documentation to see what’s available. diff --git a/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md b/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md index 57b432cb1..55075cdcb 100644 --- a/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md +++ b/second-edition/src/ch12-06-writing-to-stderr-instead-of-stdout.md @@ -1,24 +1,30 @@ -## Write to `stderr` Instead of `stdout` +## Writing Error Messages 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. +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. -We can verify that, the way we've written `minigrep` 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: +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: ```text $ cargo run > output.txt @@ -33,75 +39,55 @@ file. Let’s see what *output.txt* contains: Problem parsing arguments: not enough arguments ``` -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`: +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 ```rust,ignore -extern crate minigrep; - -use std::env; -use std::process; -use std::io::prelude::*; - -use minigrep::Config; - fn main() { let args: Vec = 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"); + eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); if let Err(e) = minigrep::run(config) { - writeln!( - &mut stderr, - "Application error: {}", - e - ).expect("Could not write to stderr"); + eprintln!("Application error: {}", e); process::exit(1); } } ``` -Listing 12-23: Writing error messages to `stderr` instead -of `stdout` using `writeln!` +Listing 12-24: Writing error messages to `stderr` instead +of `stdout` using `eprintln!` -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 `>`: +After changing `println!` to `eprintln!`, let’s try running the program again +in the same way, without any arguments and redirecting `stdout` with `>`: ```text $ 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 that's expected of command line programs. +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 -redirecting standard out to a file: +If we run the program again with arguments that don’t cause an error, but still +redirect standard out to a file: ```text $ cargo run to poem.txt > output.txt @@ -117,15 +103,15 @@ 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. +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 `writeln!` macro with -`stderr`, you're now prepared to write command line applications. By using the +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.