diff --git a/second-edition/nostarch/chapter12.md b/second-edition/nostarch/chapter12.md
index 349582bfa7..9a65fac351 100644
--- a/second-edition/nostarch/chapter12.md
+++ b/second-edition/nostarch/chapter12.md
@@ -3,42 +3,46 @@
# An I/O Project
-We've learned a lot over the last few chapters. Let's take that new knowledge
-and apply it by building a project together. Along the way, we'll learn a bit
-more about Rust's standard library.
-
-So what should we build? One that uses Rust's strengths. A great use of Rust is
-for command line tools: Rust's speed, safety, 'single binary' output, and
-cross-platform support make it a good language choice for this kind of task. So
-we'll make our own version of a classic command line tool: `grep`. `grep` is
-short for "Globally search a Regular Expression and Print." In the
-simplest use case, it does this:
-
-- Takes a filename and a string as arguments.
-- Reads the file.
-- Finds lines in the file that contain the string argument.
-- Prints out those lines.
-
-In addition, we'll add one extra feature: an environment variable that will
-allow us to search for the string argument in a case-insensitive way.
-
-There's another great reason to use `grep` as an example project: a very
-fully-featured version of `grep` has already been created in Rust by a
-community member, Andrew Gallant. It's called `ripgrep`, and it's very,
-very fast. While our version of `grep` will be fairly simple, you'll have
-some of the background knowledge to understand that project if you want to see
-something more real-world.
-
-This project will bring together a number of things we learned previously:
-
-- Organize code (using what we learned in modules, Chapter 7)
-- Use vectors and strings (collections, Chapter 8)
-- Handle errors (Chapter 9)
-- Use traits and lifetimes where appropriate (Chapter 10)
-- Have tests (Chapter 11)
-
-Additionally, we'll briefly introduce closures, iterators, and trait objects,
-which Chapters XX, YY, and ZZ respectively are about to cover in detail.
+
+
+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 an
+input/output project to 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`, 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:
+
+- 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 add one extra feature to our function that `grep` doesn't have: an
+environment variable that will allow us to search for the string argument in a
+case-insensitive way.
+
+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, but this is a good real-world
+example `grep` to use for reference or ideas.
+
+This project will bring together a number of concepts you've learned so far:
+
+- Organizing code (using what we learned in modules, Chapter 7)
+- Using vectors and strings (collections, Chapter 8)
+- Handling errors (Chapter 9)
+- Using traits and lifetimes where appropriate (Chapter 10)
+- Running tests (Chapter 11)
+
+We'll also briefly introduce closures, iterators, and trait objects, which
+Chapters XX, YY, and ZZ respectively will cover in detail.
Let's create a new project with, as always, `cargo new`:
@@ -48,30 +52,45 @@ $ cargo new --bin greprs
$ cd greprs
```
-We're calling our version of `grep` 'greprs', so that we don't confuse any of
-our users into thinking that it's the more fully-featured version of `grep`
-they may already have installed on their system.
+We're calling our version 'greprs'.
+
+
## Accepting Command Line Arguments
-Our first task is to have `greprs` accept its two command line arguments. There
-are some existing libraries on crates.io that can help us do this, but since
-we're learning, we'll implement this ourselves.
+Our first task is to make `greprs` able to accept its two command line
+arguments: the filename and a string to search for. There are some existing
+libraries on crates.io that can help us do this, but since you're learning
+let's implement this ourselves.
+
+
+
+### Creating the Argument Placeholders
+
+
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;
-Chapter 16 will cover them fully. For our purposes, though, we don't need to
-understand much about how they work in order to use them. We only need to
-understand two things:
+arguments that were given to our program. We haven't discussed iterators yet,
+and we'll cover them fully in Chapter 16, but for our purposes now we only need
+to know two things about iterators:
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 as shown in Listing 12-1:
+Let's give it a try; use the code in Listing 12-1 to create the two command
+line arguments our `grep` function needs and collect them into a vector.
+
+
-
+Listing 12-1: Collect the command line arguments into a vector and print them
+out
-First, we have a `use` statement to bring the `std::env` module into scope.
-When using a function that's nested in more than one level of module, like
-`std::env::args` is, it's conventional to use `use` to bring the parent module
-into scope, rather than the function itself. `env::args` is less ambiguous than
-a lone `args`. Also, if we end up using more than one function in `std::env`,
-we only need a single `use`.
+First, we bring the `std::env` module into scope with a `use` statement so that
+we can use its `args` function. Notice we have two environments here: the
+`std::env::args` function is nested in two levels of module. In cases where the
+desired function is nested in multiple evironments it's conventional to bring
+the parent module into scope, rather than the function itself, as that allows
+you to easily use other functions from `std::env`, and is less ambiguous than
+entering a lone `args`.
+
+
On the first line of `main`, we call `env::args`, and immediately use `collect`
-to create a vector out of it. We're also explicitly annotating the type of
-`args` here: `collect` can be used to create many kinds of collections. Rust
-won't be able to infer what kind of type we want, so the annotation is
-required. We very rarely need to annotate types in Rust, but `collect` is one
-function where you often need to.
+make it into a vector. The `collect` function can be used to create many kinds
+of collections so we explictly annotate the type of `args` to specify that we
+want a string type. 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 type you want.
Finally, we print out the vector with the debug formatter, `:?`. Let's try
running our code with no arguments, and then with two arguments:
@@ -118,14 +136,25 @@ $ cargo run needle haystack
["target/debug/greprs", "needle", "haystack"]
```
-You'll notice one interesting thing: the name of the binary is the first
-argument. The reasons for this are out of the scope of this chapter, but it's
-something we'll have to remember to account for.
+
-Now that we have a way to access all of the arguments, let's find the ones we
-care about and save them in variables as shown in Listing 12-2:
+You may notice that the first argument, 0, is taken up by the name of the
+binary. We'll have to account for that when setting the other arguments and
+begin them at `1`. The reasons for this are out of the scope of this chapter,
+but it's something to remember.
+
+### Setting the Arguments
+
+Now that we have a way to access arguments, let's set the two we need for
+`grep` and save them in variables as shown in Listing 12-2:
+
+
-
-
-Remember, the program's name is the first argument, so we don't need `args[0]`.
-We've decided that the first argument will be the string we're searching for,
-so we put a reference to the first argument in the variable `search`. The
-second argument will be the filename, so we put a reference to the second
-argument in the variable `filename`. Let's try running this program again:
+Remember, the program's name takes up the first argument at `args[0]`, so we
+start at `[1]`. The first argument `greprs` will take is the string we're
+searching for, so we put a reference to the first argument in the variable
+`search`. The second argument will be the filename, so we put a reference to
+the second argument in the variable `filename`. We add descriptions to print to
+the screen to let the user know what the program is doing. Let's try running
+this program again with the arguments `test` and `sample.txt`:
```text
$ cargo run test sample.txt
@@ -165,7 +191,14 @@ Searching for test
In file sample.txt
```
-Great! There's one problem, though. Let's try giving it no arguments:
+
+
+Great, it's working! Later we'll add some error handling to deal with
+situations such as when the user provides no argmuents, but so now we'll add
+file reading capabilities.
+
+
+
+
## Reading a File
-Now that we have some variables containing the information that we need, let's
-try using them. The next step is to open the file that we want to search. To do
-that, we need a file. Create one called `poem.txt` at the root level of your
-project, and fill it up with some Emily Dickinson:
+Next we need to give our program the ability to open the file we specify in
+order to search it. First, we need a sample file to test it with---the best
+sample to use in this case is one with a small amount of text, over multiple
+lines, and with some repeition. We've given you a sample poem to use in Listing
+12-X. Create a file called `poem.txt` at the root level of your project, and
+fill it up with some Emily Dickinson.
Filename: poem.txt
@@ -202,16 +240,17 @@ How public, like a frog
To tell your name the livelong day
To an admiring bog!
```
+Listing 12-X
+
-With that in place, let's edit *src/main.rs* and add code to open the file as
-shown in Listing 12-3:
+With that in place, edit *src/main.rs* and add code to open the file as shown
+in Listing 12-3:
-
+Listing 12-3: Reading the contents of the file specified by the second argument
-We've added a few things. First of all, we need some more `use` statements to
-bring in the relevant parts of the 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`.
-
-In `main`, we've added three things: first, we get a handle to the file and
-open it by using the `File::open` function and passing it the name of the file
-specified in the second argument. Second, we create a mutable, empty `String`
-in the variable `contents`, then call `read_to_string` on our file handle with
-our `contents` string as the argument; `contents` is where `read_to_string`
-will place the data it reads. Finally, we print out the entire file contents,
-which is a way for us to be sure our program is working so far.
-
-Let's try running this code, specifying any string for the first argument (since
-we haven't implemented the searching part yet) and our *poem.txt* file as the
-second argument:
+First, we add some more `use` statements to bring in relevant parts of the
+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`.
+
+In `main`, we've added three things: first, we set a mutable handle to the file
+and add functionality to open it using the `File::open` function, and give it
+the filename argument so that it will open whatever we pass as the second
+argument. Second, we create a variable to hold the data the program reads in
+called `contents`, which we make a mutable, empty `String`. This will hold the
+content of the file given as the second argument. Then we call `read_to_string`
+on our file handle with our `contents` string as the argument.
+
+Finally, we print out the contents of `contents` so we can check our program is
+working so far.
+
+Let's try running this code with any string passed for the first argument
+(since we haven't implemented the searching part yet) and our *poem.txt* file
+as the second argument:
```text
$ cargo run the poem.txt
@@ -285,71 +321,89 @@ To tell your name the livelong day
To an admiring bog!
```
-Great! Our code is working. However, it's got a few flaws. Because our program
-is still small, these flaws aren't a huge deal, but as our program grows, it
-will be harder and harder to fix them in a clean way. Let's do the refactoring
-now, instead of waiting. The refactoring will be much easier to do with only
-this small amount of code.
+Great! Our code read in and printed out the content of the file. There are
+still a few flaws:
+
+
+
+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 do
+with only this small amount of code, so we'll do that now.
-## Improving Error Handling and Modularity
+## Refactoring to Improve Error Handling and Modularity
There are four problems that we'd like to fix to improve our program, and they
-all have to do with potential errors and the way the program is structured. The
-first problem is where we open the file: 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, but we're
-always assuming that it's due to the file being missing. For example, the file
-could exist, but we might not have permission to open it: right now, we print
-an error message that says the wrong thing!
-
-Secondly, our use of `expect` over and over is similar to the earlier issue we
-noted with the `panic!` on indexing if we don't pass any command line
-arguments: while it _works_, it's a bit unprincipled, and we're doing it all
-throughout our program. It would be nice to put our error handling in one spot.
-
-The third problem is that our `main` function now does two things: it parses
-arguments, and it opens up files. For such a small function, this isn't a huge
-problem. However, as we keep growing our program inside of `main`, the number of
-separate tasks in the `main` function will get larger and larger. As one
-function gains many responsibilities, it gets harder to reason about, harder to
-test, and harder to change without breaking one of its parts.
+all have to do with potential errors and the way the program is structured.
+
+The first problem is that we've used `expect` to print out an error message if
+opening the file fails, but our blanket 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, we print a blanket error message that may
+give the user the wrong advice!
+
+Secondly, we use `expect` repeatedly to deal with different errors, which
+result in index errors when no arguments are provided in the command line.
+While it _works_, it's a bit unprincipled, and we're doing it all throughout
+our program. It would be much more user friendly to put our error handling in
+one spot.
+
+
+
+Thirdly, our `main` function now performs two tasks: it parses arguments, and
+opens up files. For such a small function, this isn't a huge problem. However,
+if we keep growing our program inside of `main` alone, the number of separate
+tasks the `main` function handles will grow, and as a function gains
+responsibilities it gets harder to reason about, harder to test, and harder to
+change without breaking one of its parts. It's better to separate out
+functionality.
This also ties into our fourth problem: while `search` and `filename` are
configuration variables to our program, variables like `f` and `contents` are
used to perform our program's logic. The longer `main` gets, the more variables
-we're going to bring into scope, and the more variables we have in scope, the
-harder it is to keep track of which ones we need for which purpose. It would be
-better if we grouped the configuration variables into one structure to make
-their purpose clear.
+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.
Let's address these problems by restructuring our project.
+
+
### Separation of Concerns for Binary Projects
-These kinds of organizational problems are common to many similar kinds of
-projects, so the Rust community has developed a pattern for organizing the
-separate concerns. This pattern is useful for organizing any binary project
-you'll build in Rust, so we can justify doing this refactoring a bit earlier,
-since we know that our project fits the pattern. The pattern looks like this:
+Organizational problems are common to many similar kinds of projects, so the
+Rust community has developed a kind of guideline pattern for organizing the
+separate concerns of a program. The pattern looks like this:
1. Split your program into both a *main.rs* and a *lib.rs*.
2. Place your command line parsing logic into *main.rs*.
3. Place your program's logic into *lib.rs*.
-4. The job of the `main` function is:
+4. The job of the `main` function should be to:
* parse arguments
* set up any other configuration
* call a `run` function in *lib.rs*
* if `run` returns an error, handle that error
-Whew! The pattern sounds more complicated than it is, honestly. It's all about
-separating concerns: *main.rs* handles actually running the program, and
-*lib.rs* handles all of the actual logic of the task at hand. Let's re-work our
-program into this pattern. First, let's extract a function whose purpose is
-only to parse arguments. Listing 12-4 shows the new start of `main` that calls
-a new function `parse_config`, which we're still going to define in
-*src/main.rs*:
+
+
+The pattern is simple! It's all about separating concerns: *main.rs* handles
+running the program, and *lib.rs* handles all of the logic of the task at hand.
+Let's re-work our program into this pattern.
+
+
+
+#### Extracting the Argument Parser
+
+First, we'll extract the functionality for parsing arguments. Listing 12-4
+shows the new start of `main` that calls a new function `parse_config`, which
+we're still going to define in *src/main.rs*:
-
-
-This may seem like overkill, but we're working in small steps. After making
-this change, run the program again to verify that the argument parsing still
-works. It's good to check your progress often, so that you have a better idea
-of which change caused a problem, should you encounter one.
+
+
+This may seem like overkill for our small program, but we're working in small
+steps. After making this change, run the program again to verify that the
+argument parsing still works. It's good to check your progress often, as that
+will help you identify the cause of problems when they occur.
-### Grouping Configuration Values
+#### Grouping Configuration Values
-Now that we have a function, let's improve it. Our code still has an indication
-that there's a better design possible: we return a tuple, but then immediately
-break that tuple up into individual parts again. This code isn't bad on its
-own, but there's one other sign we have room for improvement: we called our
-function `parse_config`. The `config` part of the name is saying the two values
-we return should really be bound together, since they're both part of one
-configuration value.
+We can improve on this function. At the moment, we return a tuple, but then
+immediately break that tuple up into individual parts again, which seems a
+clear indicator that we can make it more efficient.
+
+Another indicator that there's room for improvement is the `config` part of
+`parse_config`, which implies that the two values we return should be bound
+together, since they're both part of one configuration value.
+
+
> Note: some people call this anti-pattern of using primitive values when a
> complex type would be more appropriate *primitive obsession*.
-Let's introduce a struct to hold all of our configuration. Listing 12-5 shows
-the addition of the `Config` struct definition, the refactoring of
-`parse_config`, and updates to `main`:
+
+
+We'll introduce a struct to hold all of our configuration so that the
+information is held in one place. Listing 12-5 shows the addition of the
+`Config` struct definition, the refactoring of `parse_config`, as well as a few
+updates to `main` we'll talk through:
-
-
The signature of `parse_config` now indicates that it returns a `Config` value.
-In the body of `parse_config`, we used to be returning string slices that were
-references to `String` values in `args`, but we've defined `Config` to contain
-owned `String` values. Because the argument to `parse_config` is a slice of
-`String` values, the `Config` instance can't take ownership of the `String`
-values: that violates Rust's borrowing rules, since the `args` variable in
-`main` owns the `String` values and is only letting the `parse_config` function
-borrow them.
-
-There are a number of different ways we could manage the `String` data; for
-now, we'll take the easy but less efficient route, and call the `clone` method
-on the string slices. The call to `clone` will make a full copy of the string's
-data for the `Config` instance to own, which does take more time and memory
-than storing a reference to the string data, but cloning the data makes our
-code very straightforward.
+In the body of `parse_config`, where we used to return string slices as
+references to `String` values in `args`, we've now defined `Config` to contain
+owned `String` values. This is because the `args` variable in `main` owns the
+`String` values and is only letting the `parse_config` function borrow them,
+meaning it would violate Rust's borrowing rules to let `Config take ownership
+of the `String` values, since the argument to `parse_config` is a slice of
+`String` values.
+
+
+
+There are a number of different ways we could manage the `String` data, and the
+easiest, though somewhat inefficient, route is to call the `clone` method on
+the string slices. This will make a full copy of the string's data for the
+`Config` instance to own, which does take more time and memory than storing a
+reference to the string data, but also makes our code very straightforward, so
+in this simple circumstance is a worthwhile trade-off.
+
+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 `search` and `filename` variables so that is
+now uses the fields on the `Config` struct instead.
+
+
> #### The Tradeoffs of Using `clone`
>
-> There's a tendency amongst many Rustaceans to prefer not to use `clone` to fix
-> ownership problems due to its runtime cost. In Chapter XX on iterators, we'll
-> learn how to make this situation more efficient. For now, it's okay to copy a
-> few strings to keep making progress. We're only going to be making these
-> copies once, and our filename and search string are both very small. It's
-> better to have a working program that's a bit inefficient than try to
-> hyper-optimize code on your first pass. As you get more experienced with Rust,
-> it'll be easier to skip this step, but for now, it's perfectly acceptable to
-> call `clone`.
+> There's a tendency amongst many Rustaceans to avoid using `clone` to fix
+> ownership problems because of its runtime cost. In Chapter XX on iterators,
+> you'll learn how to use more efficient methods in this kind of situation, but
+> for now, it's okay to copy a few strings to keep making progress since we'll
+> only make these copies once, and our filename and search string are both very
+> small. It's better to have a working program that's a bit inefficient than
+> try to hyper-optimize code on your first pass. As you get more experienced
+> with 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` to put the instance of `Config` that `parse_config`
-returns in a variable named `config`, and we've updated the code that was using
-the separate `search` and `filename` variables to use the fields on the
-`Config` struct instead.
-
### Creating a Constructor for `Config`
-Let's now think about the purpose of `parse_config`: it's a function that
-creates a `Config` instance. We've already seen a convention for functions that
-create instances: a `new` function, like `String::new`. Listing 12-6 shows the
-result of transforming `parse_config` into a `new` function associated with our
-`Config` struct:
+
+
+The purpose of the `parse_config function is to create a `Config` instance, but
+as we know there are other ways to create new instanes, such as the `new`
+function, like `String::new` to create a new string. We'll transform our
+`parse_config` into a `new` function associated with our `Config` struct so
+that :
+
+
-
-
-We've changed the name of `parse_config` to `new` and moved it within an `impl`
+We've changed the name of `parse_config` to `new` and moved it within an `impl`
block. We've also updated the callsite in `main`. Try compiling this again to
make sure it works.
-### Returning a `Result` from the Constructor
+
+
+### Fixing the Error Handling
-Here's our last refactoring of this method: remember how accessing the vector
-with indices 1 and 2 panics when it contains fewer than 3 items and gives a bad
-error message? Let's fix that! Listing 12-7 shows how we can check that our
-slice is long enough before accessing those locations, and panic with a better
-error message:
+Now we'll do the last refactoring of this method, and fix our error handling:
+remember that an attempt as accessing the vector with indices 1 and 2 causes
+the program to panic if it contains fewer than 3 items, and currently it gives
+a bad error message. We'll fix that now.
+
+#### Improving the Error Message
+
+In Listing 12-7 we change it so the program checks that the slice is long
+enough before accessing those locations, and if it isn't the panic returns a
+better error message:
-
-
+
+
With these extra few lines of code in `new`, let's try running our program
-without any arguments:
+without any arguments and see what error occurs:
```bash
$ cargo run
@@ -569,14 +636,18 @@ thread 'main' panicked at 'not enough arguments', src\main.rs:29
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```
-This is a bit better! We at least have a reasonable error message here.
-However, we also have a bunch of extra information that we don't want to give
-to our users. We can do better by changing the type signature of `new`. Right
-now, it returns only a `Config`, so there's no way to indicate that an error
-happened while creating our `Config`. Instead, we can return a `Result`, as
-shown in Listing 12-8:
+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.
+
+
+
+#### Returning a Result from the Constructor
+
+We can resolve this by changing the type signature of `new`. Right now, it
+returns only a `Config`, so there's no way to indicate that an error happened
+while creating our `Config`. Instead, we can return a `Result`, as shown in
+Listing 12-8:
-
-
+
+
Our `new` function now returns a `Result`, with a `Config` instance in the
-success case and a `&'static str` when an error happens. Recall from "The
-Static Lifetime" section in Chapter 10 `&'static str` is the type of string
-literals, which is what our error message is for now.
+success case and a `&'static str` in the error case. Recall from "The Static
+Lifetime" section in Chapter 10 that `&'static str` is the type for string
+literals, which is our error message type for now.
We've made two changes in the body of the `new` function: instead of calling
-`panic!` if there aren't enough arguments, we now return an `Err` value. We
-wrapped the `Config` return value in an `Ok`. These changes make the function
-conform to its new type signature.
+`panic!` when the user doesn't pass enough arguments, we now return an `Err`
+value, and we've wrapped the `Config` return value in an `Ok`. These changes
+make the function conform to its new type signature.
+
+By having `Config::new` return an `Err` value, it allows the `main` functionto
+handle the `Result` value returned from the `new` function and exit the process
+more cleanly.
### Calling `Config::new` and Handling Errors
+
+
Now we need to make some changes to `main` as shown in Listing 12-9:
-
-
+
We've added a new `use` line to import `process` from the standard library.
-In the `main` function itself, we'll handle the `Result` value returned
+
+
+
+In this listing we're using a method we haven't covered before:
+`unwrap_or_else`, which is defined on `Result` by the standard library.
+Using `unwrap_or_else` allows us some custom, non-`panic!` error handling. If
+the `Result` is an `Ok` value, this method's behavior is similar to `unwrap`:
+it returns the inner value `Ok` is wrapping. However, if the value is an `Err`
+value, this method calls a *closure*, which is an anonymous function we define
+and pass as an argument to `unwrap_or_else`. We'll be covering closures in more
+detail in Chapter XX; what you need to know in this case is that
+`unwrap_or_else` will pass the inner value of the `Err` to our closure in the
+argument `err` that appears between the vertical pipes.
+
+
+
+The error handling here is only two lines: we print out the error, then call
+`std::process::exit`, which will execute the program immediately and return the
+number that was passed as a return code. By convention, a zero indicates
+success and any other value means failure. This isn't dissimilar to the
+`panic!`-based handling we used in Listing 12-7, with the exception that we no
+longer get all the extra output. Let's try it:
-```bash
+```text
$ cargo run
Compiling greprs v0.1.0 (file:///projects/greprs)
Finished debug [unoptimized + debuginfo] target(s) in 0.48 secs
@@ -681,12 +756,15 @@ Great! This output is much friendlier for our users.
### Handling Errors from the `run` Function
-Now that we're done refactoring our configuration parsing, let's improve our
-program's logic. Listing 12-10 shows the code after extracting a function named
-`run` that we'll call from `main`. The `run` function contains the code that
-was in `main`:
+Now we're done refactoring our configuration parsing; let's improve our
+program's logic. We'll extract a function named `run` that we'll call from
+`main` and add to to our code, as shown in Listing 12-10. The `run` function
+contains the code that was in `main`:
+
+
-
+The `run` function now holds the lines that were previously in `main`, and
+takes a `Config` as an argument.
-
+
-The contents of `run` are the previous lines that were in `main`, and the `run`
-function takes a `Config` as an argument. Now that we have a separate function,
-we can make a similar improvement to the one we made to `Config::new` in
-Listing 12-8: let's return a `Result` instead of calling `panic!` via
-`expect`. Listing 12-11 shows the addition of a `use` statement to bring
-`std::error::Error` struct into scope and the changes to the `run` function
-to return a `Result`:
+#### Heading?
+
+With this function separated, we'll improve the error handling like we did with
+`Config::new` in Listing 12-8: rather than allowing the program to panic, it
+will return a `Result` when something goes wrong. In Listing 12-11 we add
+a `use` statement to bring the `std::error::Error` struct into scope and we
+change the `run` function to return a `Result`:
-
-
-We've made three big changes here. The first is the return type of the `run`
-function is now `Result<(), Box>`. Previously, our function returned the
-unit type, `()`, so that's still the value returned in the `Ok` case. For our
-error type, we're going to use `Box`. This is called a *trait object*,
-which we'll be covering in Chapter XX. For now, think of it like this:
-`Box` means the function will return some kind of type that implements
-the `Error` trait, but we're not specifying what particular type the return
-value will be. This gives us flexibility to return error values that may be of
-different types in different error cases. `Box` is a smart pointer to heap
-data, and we'll be going into detail about `Box` in Chapter YY.
-
-The second change is that we've removed our calls to `expect` in favor of `?`,
+We've made three big changes here. First, we changed the return type of the
+`run` function to `Result<(), Box>`. Our function previously returned
+the unit type, `()`, and we keep that as the value returned in the `Ok` case.
+
+
+
+For our error type we use the *trait object* `Box`---we'll be covering
+trait objects in Chapter XX, but for now just know that `Box` here means
+the function will return a type that implements the `Error` trait, but that we
+don't have to specify what particular type the return value will be. This gives
+us flexibility to return error values that may be of different types in
+different error cases. We'll be go into detail about `Box` in Chapter YY.
+
+The second change we make is to remove the calls to `expect` in favor of `?`,
like we talked about in Chapter 9. Rather than `panic!` on an error, this will
-return the error value from the function we're in for the caller to handle.
+return the error value from the current function for the caller to handle.
-The third change is that we're now returning an `Ok` value from this function
-in the success case. Because we've declared the `run` function's success type
-as `()` in the signature, we need to wrap the unit type value in the `Ok`
-value. `Ok(())` looks a bit strange at first, but using `()` in this way is the
-idiomatic way to indicate that we're calling `run` for its side effects only;
-it doesn't return anything interesting.
+Thirdly, this function now returns an `Ok` value in the success case. We've
+declared the `run` function's success type as `()` in the signature, which
+means we need to wrap the unit type value in the `Ok` value. This `Ok(())`
+syntax may look a bit strange at first, but using `()` like is the idiomatic
+way to indicate that we're calling `run` for its side effects only; it doesn't
+return anything we actually need.
-This will compile, but with a warning:
+When you run this, it will compile, but with a warning:
```text
warning: unused result which must be used, #[warn(unused_must_use)] on by default
@@ -789,10 +865,19 @@ warning: unused result which must be used, #[warn(unused_must_use)] on by defaul
| ^^^^^^^^^^^^
```
-Rust is trying to tell us that we're ignoring our `Result`, which might be an
-error value. Let's handle that now. We'll use a similar technique as the way we
-handled failure with `Config::new` in Listing 12-9, but with a slight
-difference:
+Rust is telling us that our code ignores our `Result`, which might be an error
+value.
+
+
+
+Let's rectify that now.
+
+#### Heading
+
+We'll use a similar technique to the way we handled failure with `Config::new`
+in Listing 12-9, but with a slight difference:
+
+
Filename: src/main.rs
@@ -813,32 +898,35 @@ fn main() {
-Instead of `unwrap_or_else`, we use `if let` to see if `run` returns an `Err`
-value and call `process::exit(1)` if so. Why? The distinction between this case
-and the `Config::new` case is a bit subtle. With `Config::new`, we cared about
-two things:
+We use `if let` to check whether `run` returns an `Err` value, rather than
+`unwrap_or_else`, and call `process::exit(1)` if it does.
-1. Detecting any errors that happen
-2. Getting a `Config` if no errors happened
+
-In this case, because `run` returns a `()` in the success case, the only thing
-we care about is the first case: detecting an error. If we used
-`unwrap_or_else`, we'd get its return value, which would be `()`. That's not
-very useful.
+There are a couple of reasons we use a different method here. With
+`Config::new`, we cared about two things: detecting errors and getting a
+`Config` there are no errors.
-The bodies of the `if let` and of the `unwrap_or_else` are the same in both
-cases though: we print out an error and exit.
+In this case, though, because `run` returns a `()` in the success case, we only
+care about detecting an error, so we don't need `unwrap_or_else` to return its
+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 an error and exit.
### Split Code into a Library Crate
-This is looking pretty good! There's one more thing we haven't done yet: split
-the *src/main.rs* up and put some code into *src/lib.rs* Let's do that now:
-move the `run` function from *src/main.rs* to a new file, *src/lib.rs*. You'll
+This is looking pretty good so far! Now we need to split the *src/main.rs* file
+up and put some code into *src/lib.rs*
+
+
+
+Move the `run` function from *src/main.rs* to a new file, *src/lib.rs*. You'll
also need to move the relevant `use` statements and the definition of `Config`
-and its `new` method as well. Your *src/lib.rs* should now look like Listing
-12-12:
+and its `new` method in the XXX file as well. Your *src/lib.rs* should now look
+like Listing 12-12:
-
-
-Notice we also made liberal use of `pub`: on `Config`, its fields and its `new`
+We've made liberal use of `pub` here, on `Config`, its fields and its `new`
method, and on the `run` function.
-Now in *src/main.rs*, we need to bring in the code that's now in *src/lib.rs*
-through `extern crate greprs`. Then we need to add a `use greprs::Config` line
-to bring `Config` into scope, and prefix the `run` function with our crate name
-as shown in Listing 12-13:
+
+
+#### Calling the Library Code
+
+Now we need to call the code we moved to *src/lib.rs* in our *src/main.rs*, by
+using `extern crate greprs`. Then we'll add a `use greprs::Config` line to
+bring `Config` into scope, and prefix the `run` function with our crate name as
+shown in Listing 12-13:
-
-
-With that, everything should work again. Give it a few `cargo run`s and make
-sure you haven't broken anything. Whew! That all was a lot of work, but we've
-set ourselves up for success in the future. We've set up a way to handle errors
-in a much nicer fashion, and we've made our code slightly more modular. Almost
-all of our work will be done in *src/lib.rs* from here on out.
+With that, all the functionality should be connected and should work. Give it a
+few `cargo run`s and make sure you haven't broken anything.
+
+
+
+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
+modular. Almost all of our work will be done in *src/lib.rs* from here on out.
Let's take advantage of this newfound modularity by doing something that would
have been hard with our old code, but is easy with our new code: write some
@@ -947,23 +1034,36 @@ tests!
## Testing the Library's Functionality
-Writing tests for the core functionality of our code is now easier since we
-extracted the logic into *src/lib.rs* and left all the argument parsing and
-error handling in *src/main.rs*. We can now call our code directly with various
-arguments and check return values without having to call our binary from the
-command line.
-
-We're going to write a function named `grep` that takes our search term and the
-text to search and produces a list of search results. Let's remove that
-`println!` from `run` (and from *src/main.rs* as well, as we don't really need
-those anymore either), and call the new `grep` function with the options we've
-collected. We'll add a placeholder implementation of the function for now, and
+Now we've extracted the logic into *src/lib.rs* and left all the argument
+parsing and error handling in *src/main.rs*, it's much easier for us to write
+tests for the core functionality of our code.
+
+Now we can call our code directly with various arguments and check return
+values without having to call our binary from the command line.
+
+
+
+For our test we'll write a function named `grep` that takes our search term and
+the text to search and produces a list of search results.
+
+### Testing to Fail
+
+
+
+First, since we don't really need them any more, let's remove `println!` from
+`run` from both lib.rs and *src/main.rs*. Then from lib.rs we'll call the new
+`grep` function with the options we've collected.
+
+
+
+We'll add a placeholder implementation of the `grep` function for now, and add
a test that specifies the behavior we'd like the `grep` function to have. The
-test will fail with our placeholder implementation, of course, but we can make
-sure the code compiles and that we get the failure message we expect. Listing
-12-14 shows these modifications:
+test will fail with our placeholder implementation, of course, but with this we
+can make sure the code compiles and that we get the failure message we expect.
+Listing 12-14 shows these modifications:
-
-
Notice that we need an explicit lifetime `'a` declared in the signature of
-`grep` and used with the `contents` argument and the return value. Remember,
-lifetime parameters are used to specify which arguments' lifetimes connect to
-the lifetime of the return value. In this case, we're indicating that the
-vector we're returning is going to contain string slices that reference slices
-of the argument `contents`, as opposed to referencing slices of the argument
-`search`. Another way to think about what we're telling Rust is that the data
-returned by the `grep` function will live as long as the data passed into this
-function in the `contents` argument. This is important! Given that the data a
-slice references needs to be valid in order for the reference to be valid, if
-the compiler thought that we were making string slices of `search` rather than
-`contents`, it would do its safety checking incorrectly. If we tried to compile
-this function without lifetimes, we would get this error:
+`grep` and used with the `contents` argument and the return value. Remember, we
+use lifetime parameters to specify which argument lifetime is connected to the
+lifetime of the return value. In this case, we're indicating that the returned
+vector should contain string slices that reference slices of the argument
+`contents` (rather than the argument `search`).
+
+In other words, we're telling Rust that the data returned by the `grep`
+function will live as long as the data passed into the `grep` function in the
+`contents` argument. This is important! The data referenced *by* a slice needs
+to be valid in order for the reference to be valid; if the compiler assumed we
+were making string slices of `search` rather than `contents`, it would do its
+safety checking incorrectly. If we tried to compile this function without
+lifetimes, we would get this error:
```text
error[E0106]: missing lifetime specifier
@@ -1038,16 +1134,15 @@ error[E0106]: missing lifetime specifier
`contents`
```
-Rust can't possibly know which of the two arguments we need, so it needs us to
-tell it. Because `contents` is the argument that contains all of our text and
-we want to return the parts of that text that match, we know `contents` is the
+Rust can't possibly know which of the two arguments we need, so we need to tell
+it. Because `contents` is the argument that contains all of our text and we
+want to return the parts of that text that match, we know `contents` is the
argument that should be connected to the return value using the lifetime syntax.
-Connecting arguments to return values in the signature is something that other
-programming languages don't make you do, so don't worry if this still feels
-strange! Knowing how to specify lifetimes gets easier over time, and practice
-makes perfect. You may want to re-read the above section or go back and compare
-this example with the Lifetime Syntax section in Chapter 10.
+Other programming languages don't require you to connect arguments to return
+values in the signature, so this may feel strange at first, but will get easier
+over time. You may want to compare this example with the Lifetime Syntax
+section in Chapter 10 to help you get your head around it.
Now let's try running our test:
@@ -1076,18 +1171,27 @@ test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured
error: test failed
```
-Great, our test fails, exactly as we expected. Let's get the test to pass! It's
-failing because we always return an empty vector. Here's what we're going to do
-to implement `grep`:
+Great, our test fails, exactly as we expected. Let's get the test to pass!
+
+### Testing to Succeed
+
+
+
+Currently, our test is failing because we always return an empty vector. To fix
+that and implement `grep`, our program needs to follow these steps:
1. Iterate through each line of the contents.
2. Check if the line contains our search string.
* If it does, add it to the list of values we're returning.
- * If not, do nothing.
+ * If it doesn't, do nothing.
3. Return the list of results that match.
-Let's take each step at a time, starting with iterating through lines. Strings
-have a helpful method to handle this, conveniently named `lines`:
+Let's take each step at a time, starting with iterating through lines.
+
+#### Iterating Through Lines with lines
+
+Rust has a helpful method to handle line-by-line iteration of strings,
+conveniently named `lines`, that works like this:
Filename: src/lib.rs
@@ -1099,12 +1203,20 @@ fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
}
```
+Listing 12-X: Iterating through lines
+
-We're using a `for` loop along with the `lines` method to get each line in turn.
-Next, let's see if our line contains the search string. Luckily, strings have a
-helpful method named `contains` that does this for us! Using the `contains`
-method looks like this:
+We use a `for` loop along with the `lines` method to get each line in turn.
+
+
+
+#### Searching the Line for a String
+
+Next, we'll add functionality to check if the current line contains the search
+string. Luckily, strings have another helpful method named `contains` that does
+this for us! Add the `contains` method to our program so far like this:
Filename: src/lib.rs
@@ -1118,12 +1230,16 @@ fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
}
```
+Listing 12-X: Adding functionality to search for a string
+
-Finally, we need a way to store the lines that contain our search string. For
-that, we can make a mutable vector before the `for` loop and call the `push`
-method to store a `line` in the vector. After the `for` loop, we return the
-vector:
+#### Storing Matching Lines
+
+Finally, we need a way to store the lines that do contain our search string.
+For that, we can make a mutable vector before the `for` loop and call the
+`push` method to store a `line` in the vector. After the `for` loop, we return
+the vector:
Filename: src/lib.rs
@@ -1141,9 +1257,12 @@ fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
}
```
+Listing 12-X: Storing the results
+
-Let's give it a try:
+We push the lines with the matching content to the `line` vector. Let's run
+this and give it a try:
```text
$ cargo test
@@ -1165,16 +1284,23 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```
-Great! It works. Now that our test is passing, we could consider opportunities
+Our reults passed and nothing failes, great, it works!
+
+
+
+
-Now that the `grep` function is working, we need to do one last thing inside of
-the `run` function: we never printed out the results! We'll do that by adding
-a `for` loop that prints each line returned from the `grep` function:
+#### Returning Matching Lines
+
+With the functionality working, now we just need to print out the results.
+We'll do that by adding a `for` loop that prints each line returned from the
+`grep` function:
Filename: src/lib.rs
@@ -1195,6 +1321,8 @@ pub fn run(config: Config) -> Result<(), Box> {
+
+
Now our whole program should be working! Let's try it out:
```text
@@ -1217,22 +1345,38 @@ To tell your name the livelong day
To an admiring bog!
```
+
+
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.
+
+
## Working with Environment Variables
-Let's add one more feature: case insensitive searching. In addition, this
-setting won't be a command line option: it'll be an environment variable
-instead. We could choose to make case insensitivity a command line option, but
-our users have requested an environment variable that they could set once and
-make all their searches case insensitive in that terminal session.
+We'll improve our tool with one extra feature: an option for case insensitive
+searching. We could make this a command line option and require they 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 case insensitive in that terminal session.
### Implement and Test a Case-Insensitive `grep` Function
First, let's add a new function that we will call when the environment variable
-is on. Let's start by adding a new test and re-naming our existing one:
+is on.
+
+
+
+Let's start by adding a new test and re-naming our existing one:
+
+
```rust,ignore
#[cfg(test)]
@@ -1273,9 +1417,11 @@ Trust me.";
-We're going to define a new function named `grep_case_insensitive`. Its
-implementation will be almost the same as the `grep` function, but with some
-minor changes:
+We're going to define a new function named `grep_case_insensitive`, shown in
+Listing 12-X. It will be almost the same as the `grep` function, but with some
+minor changes. We'll lowercase the `search` function and `line` so that,
+whatever the case of the input arguments, they will be the same case when
+matched.
Filename: src/lib.rs
@@ -1294,17 +1440,23 @@ fn grep_case_insensitive<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
}
```
+Listing 12-X: The grep_case_insensitive_ function
+
+
First, we lowercase the `search` string, and store it in a shadowed variable
with the same name. Note that `search` is now a `String` rather than a string
-slice, so we need to add an ampersand when we pass `search` to `contains` since
-`contains` takes a string slice.
+slice which means that, because it contains takes a string slice, we need to
+reference `search` by adding an ampersand when we pass `search` to `contains`.
Second, we add a call to `to_lowercase` each `line` before we check if it
-contains `search`. Since we've converted both `line` and `search` into all
-lowercase, we'll find matches no matter what case they used in the file and the
-command line arguments, respectively. Let's see if this passes the tests:
+contains `search`. Now we've converted both `line` and `search` to all
+lowercase, so we'll find matches no matter what case the user input in the file
+and the command line arguments, respectively.
+
+Let's see if this passes the tests:
```text
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
@@ -1329,8 +1481,8 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```
-Great! Now, we have to actually use the new `grep_case_insensitive` function.
-First, let's add a configuration option for it to the `Config` struct:
+Great! Now, let's actually use the new `grep_case_insensitive` function. First,
+we need to add a configuration option for it to the `Config` struct:
Filename: src/lib.rs
@@ -1344,8 +1496,9 @@ pub struct Config {
-And then check for that option inside of the `run` function, and decide which
-function to call based on the value of the `case_sensitive` function:
+We add the case_sensitive function that takes a bool. Then we need our `run`
+function to be able to check for the case_sensitive function, and for it decide
+which function to call based on the value of the `case_sensitive` function:
Filename: src/lib.rs
@@ -1373,8 +1526,8 @@ pub fn run(config: Config) -> Result<(), Box>{
Finally, we need to actually check the environment for the variable. To bring
-the `env` module from the standard library into our project, we add a `use` line
-at the top of *src/lib.rs*:
+the `env` module from the standard library into our project, we add a `use`
+line at the top of *src/lib.rs*:
Filename: src/lib.rs
@@ -1415,17 +1568,23 @@ impl Config {
-Here, we call `env::vars`, which works in a similar way as `env::args`. The
-difference is `env::vars` returns an iterator of environment variables rather
-than command line arguments. Instead of using `collect` to create a vector of
-all of the environment variables, we're using a `for` loop. `env::vars` returns
-tuples: the name of the environment variable and its value. We never care about
-the values, only if the variable is set at all, so we use the `_` placeholder
-instead of a name to let Rust know that it shouldn't warn us about an unused
-variable. Finally, we have a `case_sensitive` variable, which is set to true by
-default. If we ever find a `CASE_INSENSITIVE` environment variable, we set the
-`case_sensitive` variable to false instead. Then we return the value as part of
-the `Config`.
+Here, we call `env::vars`, which returns an iterator of environment variables
+in the same way `env::args` returns an iterator of command line arguments.
+Instead of using `collect` to create a vector of all of the environment
+variables, we're using a `for` loop. `env::vars` returns two tuples: the name
+of the environment variable and its value.
+
+
+
+In this case we don't need to know the value of the XXX, only whether the
+variable is set, so we use the `_` placeholder instead of a name to let Rust
+know that it shouldn't warn us about an unused variable.
+
+Finally, we have a `case_sensitive` variable, which is set to true by default.
+If a `CASE_INSENSITIVE` environment variable is found, the `case_sensitive`
+variable is set to false instead. Then we return the value as part of the
+`Config`.
Let's give it a try!
@@ -1447,49 +1606,62 @@ To tell your name the livelong day
To an admiring bog!
```
-Excellent! Our `greprs` program can now do case insensitive searching controlled
-by an environment variable. Now you know how to manage options set using
-either command line arguments or environment variables!
+Excellent! Our `greprs` program can now do case insensitive searching,
+controlled by an environment variable. Now you know how to manage options set
+using either command line arguments or environment variables!
Some programs allow both arguments _and_ environment variables for the same
-configuration. In those cases, the programs decide that one or the other of
-arguments or environment variables take precedence. For another exercise on
-your own, try controlling case insensitivity through a command line argument as
-well, and decide which should take precedence if you run the program with
-contradictory values.
+configuration. In those cases, the programs decide that one or the other takes
+precedence. For another exercise on your own, try controlling case
+insensitivity through a command line argument as well as through the
+environement variabble, and decide which should take precedence 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.
+
+
## Write to `stderr` Instead of `stdout`
Right now, we're writing all of our output to the terminal with `println!`.
-This works, but most terminals provide two kinds of output: "standard out" is
-used for most information, but "standard error" is used for error messages. This
-makes it easier to do things like "Print error messages to my terminal, but
-write other output to a file."
+Most terminals provide two kinds of output: "standard out" for general
+information, and "standard error" for error messages. This distinction makes it
+easier to direct text; for example we could print error messages to the
+terminal, but write other output to a file.
+
+
-We can see that our program is only capable of printing to `stdout` by
-redirecting it to a file using `>` on the command line, and running our program
-without any arguments, which causes an error:
+Let's send some output to a file rather than to the terminal by redirecting the
+output using `>` and specifying the file. We run this on the command line with
+our program, without any arguments, and if it causes an error:
```text
$ cargo run > output.txt
```
+
+
The `>` syntax tells the shell to write the contents of standard out to
-*output.txt* instead of the screen. However, if we open *output.txt* after
-running we'll see our error message:
+*output.txt* instead of the screen. If we open *output.txt* after running we'll
+see our error message:
```text
Application error: No search string or filename found
```
-We'd like this to be printed to the screen instead, and only have the output
-from a successful run end up in the file if we run our program this way. Let's
-change how error messages are printed as shown in Listing 12-15:
+
+
+It's much more useful for error messaages like this to be printed to the screen
+instead, and only have the output from a successful run end up in the file.
+Let's change how error messages are printed as shown in Listing 12-15:
-
-
Rust does not have a convenient function like `println!` for writing to
-standard error. Instead, we use the `writeln!` macro, which is sort of like
-`println!`, but it 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.
+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 `>`:
@@ -1555,8 +1722,7 @@ try it again with arguments that work:
$ cargo run to poem.txt > output.txt
```
-We'll see no output to our terminal, but `output.txt` will contain
-our results:
+We'll see no output to our terminal, but `output.txt` will contain our results:
Filename: output.txt
@@ -1567,14 +1733,13 @@ How dreary to be somebody!
## Summary
-In this chapter, we've covered how to do common I/O operations in a Rust
-context. By using command line arguments, files, environment variables, and the
-ability to write to `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. We also saw a real-world
-scenario where lifetime annotations are needed to ensure references are
-always valid.
-
-Next, let's explore how to make use of some features of Rust that were
-influenced by functional languages: closures and iterators.
+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 `stderr` tool, 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.