Code and text changes

This commit is contained in:
Carol (Nichols || Goulding)
2017-07-13 18:59:38 -04:00
parent 3aee9217a8
commit b693c84008
9 changed files with 734 additions and 856 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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 youve learned so far and an
exploration of a few more standard library features. Were going to build a
@@ -8,24 +8,25 @@ practice some of the Rust you now have under your belt.
Rusts speed, safety, *single binary* output, and cross-platform support make
it a good language for creating command line tools, so for our project well
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. Itll 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, well 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 youve learned so far:

View File

@@ -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 youre learning, lets 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 well need a function provided in
Rusts standard library: `std::env::args`. This function returns an *iterator*
of the command line arguments that were given to our program. We havent
discussed iterators yet, and well 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.
Lets 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.
<span class="filename">Filename: src/main.rs</span>
@@ -52,24 +52,26 @@ desired function is nested in more than one module, its conventional to bring
the parent module into scope, rather than the function itself. This lets us
easily use other functions from `std::env`. Its 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 thats 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. Weve 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 isnt 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, `:?`. Lets 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.
Thats 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. Lets do
that as shown in Listing 12-2:
<span class="filename">Filename: src/main.rs</span>
@@ -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 programs name takes up the first
value in the vector at `args[0]`, so that were starting at index `1`. The
first argument `minigrep` takes is the string were 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, its working! The values of the arguments we need are being saved into
the right variables. Later well add some error handling to deal with certain
potential erroneous situations, such as when the user provides no arguments,
but for now well ignore that and work on adding file reading capabilities
instead.

View File

@@ -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, were 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 “Im
nobody! Who are you?”:
<span class="filename">Filename: poem.txt</span>
@@ -35,18 +36,20 @@ use std::fs::File;
use std::io::prelude::*;
fn main() {
let args: Vec<String> = env::args().collect();
let query = &args[1];
let filename = &args[2];
println!("Searching for {}", query);
# let args: Vec<String> = 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 youll need when working with I/O. Unlike the default
prelude, we must explicitly `use` the prelude from `std::io`.
In `main`, weve 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, weve 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.
Lets try running this code with any string as the first command line argument
(since we havent 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. Weve 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 were not handling errors as well
as we could be. While our program is still small, these flaws arent a big
problem, but as our program grows, it will be harder to fix them cleanly. Its
good practice to begin refactoring early on when developing a program, as its
much easier to refactor smaller amounts of code, so well do that now.

View File

@@ -19,12 +19,12 @@ were 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. Its 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 weve 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 were in that situation, wed 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, theyll get an
@@ -39,23 +39,23 @@ Lets 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
programs 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 @@ programs 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. Lets re-work our program by following this process.
### Extracting the Argument Parser
#### Extracting the Argument Parser
First, well 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 @@ Were 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 were refactoring in
@@ -183,7 +183,7 @@ since we dont 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`
>
> Theres 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, itll be easier to go straight to the desirable method, but for now its
> 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.
Weve 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`, well be able to
create instances of `Config` by calling `Config::new`. Listing 12-7 shows the
changes well need to make:
<span class="filename">Filename: src/main.rs</span>
@@ -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 wont really help our end users
understand what happened and what they should do instead. Lets 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, were 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
isnt long enough, the program panics, with a better error message than the
`index out of bounds` message:
<span class="filename">Filename: src/main.rs</span>
@@ -302,13 +302,13 @@ fn new(args: &[String]) -> Config {
<span class="caption">Listing 12-8: Adding a check for the number of
arguments</span>
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, were 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`, lets 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 dont want to give to our users. So
perhaps using the technique we used in Listing 9-8 isnt 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`:
<span class="filename">Filename: src/main.rs</span>
@@ -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. Were 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.
<span class="filename">Filename: src/main.rs</span>
@@ -418,12 +418,11 @@ pipes. The code in the closure can then use the `err` value when it runs.
Weve 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. Lets 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 were done refactoring our configuration parsing; lets turn to our
programs logic. As we laid out in the “Separation of Concerns for Binary
Projects section, were 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 were done, `main` will be concise and
easy to verify by inspection, and well 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, were making only
the small, incremental improvement of extracting the function. Were still
defining the function in *src/main.rs*:
<span class="filename">Filename: src/main.rs</span>
@@ -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<T, E>` 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<T, E>` 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`:
<span class="filename">Filename: src/main.rs</span>
@@ -554,8 +553,8 @@ to have some error handling code here! Lets 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
Well 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:
<span class="filename">Filename: src/main.rs</span>
@@ -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 were 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*:
Lets 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):
<span class="filename">Filename: src/lib.rs</span>
@@ -615,26 +615,12 @@ pub struct Config {
impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
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<Error>>{
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<Error>> {
// ...snip...
}
```
@@ -643,14 +629,12 @@ pub fn run(config: Config) -> Result<(), Box<Error>>{
Weve 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 well 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 well 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:
<span class="filename">Filename: src/main.rs</span>
@@ -663,20 +647,9 @@ use std::process;
use minigrep::Config;
fn main() {
let args: Vec<String> = 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() {
<span class="caption">Listing 12-14: Bringing the `minigrep` crate into the
scope of *src/main.rs*</span>
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 well 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 weve set ourselves up for success in the
future. Now its much easier to handle errors, and weve made our code more

View File

@@ -10,21 +10,21 @@ In this section, were 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`.
Were 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. Were going to add this functionality in
a function called `search`.
### Writing a Failing Test
@@ -65,23 +65,22 @@ Pick three.";
<span class="caption">Listing 12-15: Creating a failing test for the `search`
function we wish we had</span>
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 were
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 arent able to run this test and watch it fail though, since this test
doesnt even compilethe search function doesn't exist yet! So now well 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 doesnt match a vector containing the line `"safe, fast, productive."`.
<span class="filename">Filename: src/lib.rs</span>
```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. Lets 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 were returning.
* If it doesnt, do nothing.
* Return the list of results that match.
Lets 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:
<span class="filename">Filename: src/lib.rs</span>
```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:
<span class="filename">Filename: src/lib.rs</span>
```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:
<span class="filename">Filename: src/lib.rs</span>
```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 isnt too bad, but it isnt taking advantage of some useful
features of iterators. Well be coming back to this example in Chapter 13 where
well 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<Error>> {
}
```
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.
Were still using a `for` loop to get each line returned from `search` and
printing out each line.
Now our whole program should be working! Lets 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! Weve built our own mini version of a classic tool, and learned a
lot about how to structure applications. Weve 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, were 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 youd like at this point.

View File

@@ -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.
Were 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 were 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:
Were going to continue following the TDD process, so the first step is again
to write a failing test. Well 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:
<span class="filename">Filename: src/lib.rs</span>
@@ -60,30 +60,29 @@ Trust me.";
<span class="caption">Listing 12-20: Adding a new failing test for the case
insensitive function were about to add</span>
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 weve edited the old tests `contents` too. We've added a new line
with the text Duct tape, with a capital D, that shouldnt match the query
duct when were searching in a case sensitive manner. Changing the old test
in this way helps ensure that we dont accidentally break the case sensitive
search functionality that weve 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 were 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 havent 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 well 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.
<span class="filename">Filename: src/lib.rs</span>
@@ -111,42 +110,26 @@ matter if the users 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 weve converted both
`line` and `query` to lowercase, well find matches no matter what the case of
the query.
Lets 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, lets actually call the new `search_case_insensitive` function from
@@ -215,10 +198,10 @@ pub fn run(config: Config) -> Result<(), Box<Error>>{
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 were 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:
<span class="filename">Filename: src/lib.rs</span>
@@ -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`</span>
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:
Were using the `is_err` method on the `Result` to check to see if its 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 dont care
about the *value* of the environment variable, just whether its set or unset,
so were checking `is_err` rather than `unwrap`, `expect`, or any of the other
methods weve 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.
Lets give it a try! First, well 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, lets 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 whats available.

View File

@@ -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 were 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, lets 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. Well 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. Lets 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. Its 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. Well change that.
### Printing Errors to Standard Error
Lets 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:
<span class="filename">Filename: src/main.rs</span>
```rust,ignore
extern crate minigrep;
use std::env;
use std::process;
use std::io::prelude::*;
use minigrep::Config;
fn main() {
let args: Vec<String> = env::args().collect();
let mut stderr = std::io::stderr();
let config = Config::new(&args).unwrap_or_else(|err| {
writeln!(
&mut stderr,
"Problem parsing arguments: {}",
err
).expect("Could not write to stderr");
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);
}
}
```
<span class="caption">Listing 12-23: Writing error messages to `stderr` instead
of `stdout` using `writeln!`</span>
<span class="caption">Listing 12-24: Writing error messages to `stderr` instead
of `stdout` using `eprintln!`</span>
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!`, lets 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 dont 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 were now using `stdout` for successful output and
`stderr` for error output as appropriate.
## Summary
In this chapter, weve 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`, youre 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.