mirror of
https://github.com/rust-lang/book.git
synced 2026-09-15 07:49:25 -04:00
Code and text changes
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# An I/O Project Building a Small Grep
|
||||
# An I/O Project: Building a Command Line Program
|
||||
|
||||
This chapter is both a recap of the many skills you’ve learned so far and an
|
||||
exploration of a few more standard library features. We’re going to build a
|
||||
@@ -8,24 +8,25 @@ practice some of the Rust you now have under your belt.
|
||||
Rust’s speed, safety, *single binary* output, and cross-platform support make
|
||||
it a good language for creating command line tools, so for our project we’ll
|
||||
make our own version of the classic command line tool `grep`. Grep is an
|
||||
acronym for "Globally search a Regular Expression and Print." In the simplest
|
||||
use case, `grep` searches a specified file for a specified string using the
|
||||
following steps:
|
||||
acronym for “Globally search a Regular Expression and Print.” In the simplest
|
||||
use case, `grep` searches a specified file for a specified string. To do so,
|
||||
`grep` takes a filename and a string as its arguments, then reads the file and
|
||||
finds lines in that file that contain the string argument. It’ll then print out
|
||||
those lines.
|
||||
|
||||
- Take as arguments a filename and a string.
|
||||
- Read the file.
|
||||
- Find lines in the file that contain the string argument.
|
||||
- Print out those lines.
|
||||
|
||||
We'll also show how to use environment variables and print to standard error
|
||||
instead of standard out; these techniques are commonly used in command line
|
||||
tools.
|
||||
Along the way, we’ll show how to make our command line tool use features of the
|
||||
terminal that many command line tools use. We'll read the value of an
|
||||
environment variable in order to allow the user to configure the behavior of
|
||||
our tool. We'll print to the standard error console stream (`stderr`) instead
|
||||
of standard output (`stdout`) so that, for example, the user can choose to
|
||||
redirect successful output to a file while still seeing error messages on the
|
||||
screen.
|
||||
|
||||
One Rust community member, Andrew Gallant, has already created a
|
||||
fully-featured, very fast version of `grep`, called `ripgrep`. By comparison,
|
||||
our version of `grep` will be fairly simple, this chapter will give you some of
|
||||
the background knowledge to help you understand a real-world project like
|
||||
`ripgrep`.
|
||||
our version of `grep` will be fairly simple, but this chapter will give you
|
||||
some of the background knowledge to help you understand a real-world project
|
||||
like `ripgrep`.
|
||||
|
||||
This project will bring together a number of concepts you’ve learned so far:
|
||||
|
||||
|
||||
@@ -9,26 +9,26 @@ file to search in, like so:
|
||||
$ cargo run searchstring example-filename.txt
|
||||
```
|
||||
|
||||
Right now, the program generated by `cargo new` ignores any arguments we give
|
||||
it. There are some existing libraries on crates.io that can help us accept
|
||||
command line arguments, but since we're learning, let's implement this
|
||||
Right now, the program generated by `cargo new` cannot process arguments we
|
||||
give it. There are some existing libraries on crates.io that can help us accept
|
||||
command line arguments, but since you’re learning, let’s implement this
|
||||
ourselves.
|
||||
|
||||
### Reading the Argument Values
|
||||
|
||||
In order to be able to get the values of command line arguments passed to our
|
||||
program, we'll need to call a function provided in Rust's standard library:
|
||||
`std::env::args`. This function returns an *iterator* of the command line
|
||||
arguments that were given to our program. We haven't discussed iterators yet,
|
||||
and we'll cover them fully in Chapter 13, but for our purposes now we only need
|
||||
to know two things about iterators:
|
||||
We first need to make sure our program is able to get the values of command
|
||||
line arguments we pass to it, for which we’ll need a function provided in
|
||||
Rust’s standard library: `std::env::args`. This function returns an *iterator*
|
||||
of the command line arguments that were given to our program. We haven’t
|
||||
discussed iterators yet, and we’ll cover them fully in Chapter 13, but for our
|
||||
purposes now we only need to know two things about iterators: Iterators produce
|
||||
a series of values, and we can call the `collect` function on an iterator to
|
||||
turn it into a collection, such as a vector, containing all of the elements the
|
||||
iterator produces.
|
||||
|
||||
1. Iterators produce a series of values.
|
||||
2. We can call the `collect` function on an iterator to turn it into a vector
|
||||
containing all of the elements the iterator produces.
|
||||
|
||||
Let's give it a try: use the code in Listing 12-1 to read any command line
|
||||
arguments passed to our `minigrep` program and collect them into a vector.
|
||||
Let’s give it a try: use the code in Listing 12-1 to allow your `minigrep`
|
||||
program to read any command line arguments passed it and then collect the
|
||||
values into a vector.
|
||||
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
@@ -52,24 +52,26 @@ desired function is nested in more than one module, it’s conventional to bring
|
||||
the parent module into scope, rather than the function itself. This lets us
|
||||
easily use other functions from `std::env`. It’s also less ambiguous than
|
||||
adding `use std::env::args;` then calling the function with just `args`; that
|
||||
might look like a function that's defined in the current module.
|
||||
might easily be mistaken for a function that’s defined in the current module.
|
||||
|
||||
> Note: `std::env::args` will panic if any argument contains invalid Unicode.
|
||||
> If you need to accept arguments containing invalid Unicode, use
|
||||
> ### The `args` Function and Invalid Unicode
|
||||
>
|
||||
> Note that `std::env::args` will panic if any argument contains invalid
|
||||
> Unicode. If you need to accept arguments containing invalid Unicode, use
|
||||
> `std::env::args_os` instead. That function returns `OsString` values instead
|
||||
> of `String` values. We’ve chosen to use `std::env::args` here for simplicity
|
||||
> because `OsString` values differ per-platform and are more complex to work
|
||||
> with than `String` values.
|
||||
|
||||
On the first line of `main`, we call `env::args`, and immediately use `collect`
|
||||
to turn the iterator into a vector containing all of the iterator's values. The
|
||||
`collect` function can be used to create many kinds of collections, so we
|
||||
explicitly annotate the type of `args` to specify that we want a vector of
|
||||
strings. Though we very rarely need to annotate types in Rust, `collect` is one
|
||||
function you do often need to annotate because Rust isn't able to infer what
|
||||
kind of collection you want.
|
||||
to turn the iterator into a vector containing all of the values produced by the
|
||||
iterator. The `collect` function can be used to create many kinds of
|
||||
collections, so we explicitly annotate the type of `args` to specify that we
|
||||
want a vector of strings. Though we very rarely need to annotate types in Rust,
|
||||
`collect` is one function you do often need to annotate because Rust isn’t able
|
||||
to infer what kind of collection you want.
|
||||
|
||||
Finally, we print out the vector with the debug formatter, `:?`. Let's try
|
||||
Finally, we print out the vector with the debug formatter, `:?`. Let’s try
|
||||
running our code with no arguments, and then with two arguments:
|
||||
|
||||
```text
|
||||
@@ -82,17 +84,20 @@ $ cargo run needle haystack
|
||||
```
|
||||
|
||||
You may notice that the first value in the vector is `"target/debug/minigrep"`,
|
||||
which is the name of our binary. The reasons for this are out of the scope of
|
||||
this chapter, but we'll need to remember this as we save the two arguments we
|
||||
need.
|
||||
which is the name of our binary. This matches the behavior of the arguments
|
||||
list in C, and lets programs use the name by which they were invoked in their
|
||||
execution. It's convenient to have access to the program name in case we want
|
||||
to print it in messages or change behavior of the program based on what command
|
||||
line alias was used to invoke the program, but for the purposes of this chapter
|
||||
we're going to ignore it and only save the two arguments we need.
|
||||
|
||||
### Saving the Argument Values in Variables
|
||||
|
||||
Printing out the value of the vector of arguments just illustrated that we're
|
||||
able to access the values specified as command line arguments from our program.
|
||||
That's not what we actually want to do, though, we want to save the values of
|
||||
the two arguments in variables so that we can use the values in our program.
|
||||
Let's do that as shown in Listing 12-2:
|
||||
Printing out the value of the vector of arguments has illustrated that the
|
||||
program is able to access the values specified as command line arguments.
|
||||
That’s not actually our end goal, though: we want to save the values of the two
|
||||
arguments in variables so that we can use the values in our program. Let’s do
|
||||
that as shown in Listing 12-2:
|
||||
|
||||
<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 program’s name takes up the first
|
||||
value in the vector at `args[0]`, so that we’re starting at index `1`. The
|
||||
first argument `minigrep` takes is the string we’re searching for, so we put a
|
||||
reference to the first argument in the variable `query`. The second argument
|
||||
will be the filename, so we put a reference to the second argument in the
|
||||
variable `filename`.
|
||||
@@ -131,7 +136,8 @@ Searching for test
|
||||
In file sample.txt
|
||||
```
|
||||
|
||||
Great, it's working! We're saving the values of the arguments that we need into
|
||||
the right variables. Later we'll add some error handling to deal with
|
||||
situations such as when the user provides no arguments, but for now we'll
|
||||
ignore that and work on adding file reading capabilities instead.
|
||||
Great, it’s working! The values of the arguments we need are being saved into
|
||||
the right variables. Later we’ll add some error handling to deal with certain
|
||||
potential erroneous situations, such as when the user provides no arguments,
|
||||
but for now we’ll ignore that and work on adding file reading capabilities
|
||||
instead.
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
## Reading a File
|
||||
|
||||
Next, we're going to read the file that we specify in the filename command line
|
||||
argument. First, we need a sample file to test it with---the best kind of file
|
||||
to use to make sure that `minigrep` is working is one with a small amount of text
|
||||
over multiple lines with some repeated words. Listing 12-3 has an Emily
|
||||
Dickinson poem that will work well! Create a file called `poem.txt` at the root
|
||||
level of your project, and enter the poem "I'm nobody! Who are you?":
|
||||
Next, we’re going to add functionality to read the file that specified in the
|
||||
`filename` command line argument. First, we need a sample file to test it
|
||||
with—the best kind of file to use to make sure that `minigrep` is working is
|
||||
one with a small amount of text over multiple lines with some repeated words.
|
||||
Listing 12-3 has an Emily Dickinson poem that will work well! Create a file
|
||||
called `poem.txt` at the root level of your project, and enter the poem “I’m
|
||||
nobody! Who are you?”:
|
||||
|
||||
<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 you’ll need when working with I/O. Unlike the default
|
||||
prelude, we must explicitly `use` the prelude from `std::io`.
|
||||
|
||||
In `main`, we’ve added three things: first, we get a mutable handle to the file
|
||||
by calling the `File::open` function and passing it the value of the `filename`
|
||||
@@ -70,9 +73,9 @@ mutable, empty `String`. This will hold the content of the file after we read
|
||||
it in. Third, we call `read_to_string` on our file handle and pass a mutable
|
||||
reference to `contents` as an argument.
|
||||
|
||||
After those lines, we've again added temporary `println!` that prints out the
|
||||
value in `contents` after we've read the file so we can check that our program
|
||||
is working so far.
|
||||
After those lines, we’ve again added a temporary `println!` statement that
|
||||
prints out the value of `contents` after the file is read, so that we can check
|
||||
that our program is working so far.
|
||||
|
||||
Let’s try running this code with any string as the first command line argument
|
||||
(since we haven’t implemented the searching part yet) and our *poem.txt* file
|
||||
@@ -96,10 +99,11 @@ To tell your name the livelong day
|
||||
To an admiring bog!
|
||||
```
|
||||
|
||||
Great! Our code read in and printed out the content of the file. We've got a
|
||||
few flaws though: the `main` function has multiple responsibilities, and we're
|
||||
not handling errors as well as we could be. While our program is still small,
|
||||
these flaws aren't a big problem, but as our program grows, it will be harder
|
||||
to fix them cleanly. It's good practice to begin refactoring early on when
|
||||
developing a program, as it's much easier to refactor smaller amounts of code,
|
||||
so we'll do that now.
|
||||
Great! Our code read in and printed out the content of the file. We’ve got a
|
||||
few flaws though. The `main` function has multiple responsibilities; generally
|
||||
functions are clearer and easier to maintain if each function is responsible
|
||||
for only one idea. The other problem is that we’re not handling errors as well
|
||||
as we could be. While our program is still small, these flaws aren’t a big
|
||||
problem, but as our program grows, it will be harder to fix them cleanly. It’s
|
||||
good practice to begin refactoring early on when developing a program, as it’s
|
||||
much easier to refactor smaller amounts of code, so we’ll do that now.
|
||||
|
||||
@@ -19,12 +19,12 @@ we’re going to need to bring into scope; the more variables we have in scope,
|
||||
the harder it is to keep track of the purpose of each. It’s better to group the
|
||||
configuration variables into one structure to make their purpose clear.
|
||||
|
||||
The third problem is that we've used `expect` to print out an error message if
|
||||
opening the file fails, but the error message only says `file not found`. There
|
||||
are a number of ways that opening a file can fail besides a missing file: for
|
||||
example, the file might exist, but we might not have permission to open it.
|
||||
Right now, if we're in that situation, we'd print the `file not found` error
|
||||
message that would give the user the wrong advice!
|
||||
The third problem is that we’ve used `expect` to print out an error message
|
||||
when opening the file fails, but the error message only says `file not found`.
|
||||
There are a number of ways that opening a file can fail besides the file being
|
||||
missing: for example, the file might exist, but we might not have permission to
|
||||
open it. Right now, if we’re in that situation, we’d print the `file not found`
|
||||
error message that would give the user the wrong advice!
|
||||
|
||||
Fourth, we use `expect` repeatedly to deal with different errors, and if the
|
||||
user runs our programs without specifying enough arguments, they’ll get an
|
||||
@@ -39,23 +39,23 @@ Let’s address these problems by refactoring our project.
|
||||
|
||||
### Separation of Concerns for Binary Projects
|
||||
|
||||
The organizational problem of having the `main` function responsible for
|
||||
multiple tasks is common to many binary projects, so the Rust community has
|
||||
developed a kind of guideline process for splitting up the separate concerns of
|
||||
a binary program when `main` starts getting large. The process has the
|
||||
following steps:
|
||||
The organizational problem of allocating responsibility for multiple tasks to
|
||||
the `main` function responsible is common to many binary projects, so the Rust
|
||||
community has developed a kind of guideline process for splitting up the
|
||||
separate concerns of a binary program when `main` starts getting large. The
|
||||
process has the following steps:
|
||||
|
||||
1. Split your program into both a *main.rs* and a *lib.rs* and move your
|
||||
program's logic into *lib.rs*.
|
||||
2. While your command line parsing logic is small, it can remain in *main.rs*.
|
||||
3. When the command line parsing logic starts getting complicated, extract it
|
||||
from *main.rs* into *lib.rs* as well.
|
||||
4. The responsibilities that remain in the `main` function after this process
|
||||
should be:
|
||||
* Calling the command line parsing logic with the argument values
|
||||
* Setting up any other configuration
|
||||
* Calling a `run` function in *lib.rs*
|
||||
* If `run` returns an error, handling that error
|
||||
* Split your program into both a *main.rs* and a *lib.rs* and move your
|
||||
program’s logic into *lib.rs*.
|
||||
* While your command line parsing logic is small, it can remain in *main.rs*.
|
||||
* When the command line parsing logic starts getting complicated, extract it
|
||||
from *main.rs* into *lib.rs* as well.
|
||||
* The responsibilities that remain in the `main` function after this process
|
||||
should be limited to:
|
||||
* Calling the command line parsing logic with the argument values
|
||||
* Setting up any other configuration
|
||||
* Calling a `run` function in *lib.rs*
|
||||
* If `run` returns an error, handling that error
|
||||
|
||||
This pattern is all about separating concerns: *main.rs* handles running the
|
||||
program, and *lib.rs* handles all of the logic of the task at hand. Because we
|
||||
@@ -64,7 +64,7 @@ program’s logic by moving it into functions in *lib.rs*. The only code that
|
||||
remains in *main.rs* will be small enough to verify its correctness by reading
|
||||
it. Let’s re-work our program by following this process.
|
||||
|
||||
### Extracting the Argument Parser
|
||||
#### Extracting the Argument Parser
|
||||
|
||||
First, we’ll extract the functionality for parsing arguments. Listing 12-5
|
||||
shows the new start of `main` that calls a new function `parse_config`, which
|
||||
@@ -96,10 +96,10 @@ We’re still collecting the command line arguments into a vector, but instead o
|
||||
assigning the argument value at index `1` to the variable `query` and the
|
||||
argument value at index `2` to the variable `filename` within the `main`
|
||||
function, we pass the whole vector to the `parse_config` function. The
|
||||
`parse_config` function then holds the logic that knows which argument goes in
|
||||
which variable, and passes the values back to `main`. We still create the
|
||||
`query` and `filename` variables in `main`, but `main` no longer has the
|
||||
responsibility of knowing how the command line arguments and variables
|
||||
`parse_config` function then holds the logic that determines which argument
|
||||
goes in which variable, and passes the values back to `main`. We still create
|
||||
the `query` and `filename` variables in `main`, but `main` no longer has the
|
||||
responsibility of determining how the command line arguments and variables
|
||||
correspond.
|
||||
|
||||
This may seem like overkill for our small program, but we’re refactoring in
|
||||
@@ -183,7 +183,7 @@ since we don’t have to manage the lifetimes of the references, so in this
|
||||
circumstance giving up a little performance to gain simplicity is a worthwhile
|
||||
trade-off.
|
||||
|
||||
> #### The Tradeoffs of Using `clone`
|
||||
> ### The Tradeoffs of Using `clone`
|
||||
>
|
||||
> There’s a tendency among many Rustaceans to avoid using `clone` to fix
|
||||
> ownership problems because of its runtime cost. In Chapter 13 on iterators,
|
||||
@@ -195,15 +195,15 @@ trade-off.
|
||||
> Rust, it’ll be easier to go straight to the desirable method, but for now it’s
|
||||
> perfectly acceptable to call `clone`.
|
||||
|
||||
We've updated `main` so that it places the instance of `Config` that
|
||||
`parse_config` returns into a variable named `config`, and updated the code
|
||||
that previously used the separate `query` and `filename` variables so that is
|
||||
now uses the fields on the `Config` struct instead.
|
||||
We’ve updated `main` so that it places the instance of `Config` returned by
|
||||
`parse_config` into a variable named `config`, and updated the code that
|
||||
previously used the separate `query` and `filename` variables so that it now
|
||||
uses the fields on the `Config` struct instead.
|
||||
|
||||
Our code now more clearly conveys our intent that `query` and `filename` are
|
||||
related and their purpose is to configure how the program will work. Any code
|
||||
that uses these values knows to find them in the `config` instance in the
|
||||
fields named for their purpose.
|
||||
Our code now more clearly conveys that `query` and `filename` are related and
|
||||
their purpose is to configure how the program will work. Any code that uses
|
||||
these values knows to find them in the `config` instance in the fields named
|
||||
for their purpose.
|
||||
|
||||
#### Creating a Constructor for `Config`
|
||||
|
||||
@@ -219,9 +219,9 @@ instance, we can change `parse_config` from being a plain function into a
|
||||
function named `new` that is associated with the `Config` struct. Making this
|
||||
change will make our code more idiomatic: we can create instances of types in
|
||||
the standard library like `String` by calling `String::new`, and by changing
|
||||
`parse_config` to be a `new` function associated with `Config`, we'll be able
|
||||
to create instances of `Config` by calling `Config::new`. Listing 12-7 shows
|
||||
the changes we'll need to make:
|
||||
`parse_config` into a `new` function associated with `Config`, we’ll be able to
|
||||
create instances of `Config` by calling `Config::new`. Listing 12-7 shows the
|
||||
changes we’ll need to make:
|
||||
|
||||
<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 won’t really help our end users
|
||||
understand what happened and what they should do instead. Let’s fix that now.
|
||||
|
||||
#### Improving the Error Message
|
||||
|
||||
In Listing 12-8, we're adding a check in the `new` function to check that the
|
||||
slice is long enough before accessing index 1 and 2. If the slice isn't long
|
||||
enough, we panic with a better error message than the `index out of bounds`
|
||||
message:
|
||||
In Listing 12-8, we’re adding a check in the `new` function that will check
|
||||
that the slice is long enough before accessing index `1` and `2`. If the slice
|
||||
isn’t long enough, the program panics, with a better error message than the
|
||||
`index out of bounds` message:
|
||||
|
||||
<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, we’re checking that the
|
||||
length of `args` is at least 3, and the rest of the function can operate under
|
||||
the assumption that this condition has been met. If `args` has fewer than 3
|
||||
items, this condition will be true, and we call the `panic!` macro to end the
|
||||
program immediately.
|
||||
|
||||
With these extra few lines of code in `new`, let’s try running our program
|
||||
without any arguments again and see what the error looks like now:
|
||||
@@ -325,22 +325,22 @@ This output is better, we now have a reasonable error message. However, we also
|
||||
have a bunch of extra information we don’t want to give to our users. So
|
||||
perhaps using the technique we used in Listing 9-8 isn’t the best to use here;
|
||||
a call to `panic!` is more appropriate for a programming problem rather than a
|
||||
usage problem anyway, as we discussed in Chapter 9. Instead, we can use the
|
||||
other technique we learned about in that chapter: returning a `Result` that can
|
||||
usage problem, as we discussed in Chapter 9. Instead, we can use the other
|
||||
technique you also learned about in Chapter 9: returning a `Result` that can
|
||||
indicate either success or an error.
|
||||
|
||||
#### Returning a `Result` from `new` Instead of Calling `panic!`
|
||||
|
||||
We can choose to instead return a `Result` value that will contain a `Config`
|
||||
instance in the successful case, and will describe the problem in the error
|
||||
case. When `Config::new` is communicating to `main`, we can use Rust's way of
|
||||
signaling that there was a problem using the `Result` type. Then we can change
|
||||
`main` to convert an `Err` variant into a nicer error for our users, without
|
||||
the surrounding text about `thread 'main'` and `RUST_BACKTRACE` that a call to
|
||||
case. When `Config::new` is communicating to `main`, we can use the `Result`
|
||||
type to signal that there was a problem. Then we can change `main` to convert
|
||||
an `Err` variant into a more practical error for our users, without the
|
||||
surrounding text about `thread 'main'` and `RUST_BACKTRACE` that a call to
|
||||
`panic!` causes.
|
||||
|
||||
Listing 12-9 shows the changes to the return value of `Config::new` and the
|
||||
body of the function needed to return a `Result`:
|
||||
Listing 12-9 shows the changes you need to make to the return value of
|
||||
`Config::new` and the body of the function needed to return a `Result`:
|
||||
|
||||
<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. We’re also going to take the responsibility of exiting the
|
||||
command line tool with a nonzero error code from `panic!` and implement it by
|
||||
hand. A nonzero exit status is a convention to signal to the process that
|
||||
called our program that our program ended with an error state.
|
||||
|
||||
<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.
|
||||
|
||||
We’ve added a new `use` line to import `process` from the standard library. The
|
||||
code in the closure that will get run in the error case is only two lines: we
|
||||
print out the `err` value, then call `std::process::exit` (we've added a new
|
||||
`use` line at the top to import `process` from the standard library).
|
||||
`process::exit` will stop the program immediately and return the number that
|
||||
was passed as the exit status code. This is similar to the `panic!`-based
|
||||
handling we used in Listing 12-8, with the exception that we no longer get all
|
||||
the extra output. Let's try it:
|
||||
print out the `err` value, then call `process::exit`. The `process::exit`
|
||||
function will stop the program immediately and return the number that was
|
||||
passed as the exit status code. This is similar to the `panic!`-based handling
|
||||
we used in Listing 12-8, with the exception that we no longer get all the extra
|
||||
output. Let’s try it:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@@ -435,18 +434,18 @@ Problem parsing arguments: not enough arguments
|
||||
|
||||
Great! This output is much friendlier for our users.
|
||||
|
||||
### Extracting a `run` Function
|
||||
### Extracting Logic from `main`
|
||||
|
||||
Now we're done refactoring our configuration parsing; let's turn to our
|
||||
program's logic. As we laid out in the process we discussed in the "Separation
|
||||
of Concerns for Binary Projects" section, we're going to extract a function
|
||||
named `run` that will hold all of the logic currently in the `main` function
|
||||
that isn't setting up configuration or handling errors. Once we're done, `main`
|
||||
will be concise and easy to verify by inspection, and we'll be able to write
|
||||
tests for all of the other logic.
|
||||
Now we’re done refactoring our configuration parsing; let’s turn to our
|
||||
program’s logic. As we laid out in the “Separation of Concerns for Binary
|
||||
Projects” section, we’re going to extract a function named `run` that will hold
|
||||
all of the logic currently in the `main` function not involved with setting up
|
||||
configuration or handling errors. Once we’re done, `main` will be concise and
|
||||
easy to verify by inspection, and we’ll be able to write tests for all of the
|
||||
other logic.
|
||||
|
||||
Listing 12-11 shows the extracted `run` function. For now, we're making only
|
||||
the small, incremental improvement of extracting the function and still
|
||||
Listing 12-11 shows the extracted `run` function. For now, we’re making only
|
||||
the small, incremental improvement of extracting the function. We’re still
|
||||
defining the function in *src/main.rs*:
|
||||
|
||||
<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! Let’s rectify that now.
|
||||
|
||||
#### Handling Errors Returned from `run` in `main`
|
||||
|
||||
We'll check for errors and handle them nicely using a similar technique to the
|
||||
way we handled errors with `Config::new` in Listing 12-10, but with a slight
|
||||
We’ll check for errors and handle them using a similar technique to the way we
|
||||
handled errors with `Config::new` in Listing 12-10, but with a slight
|
||||
difference:
|
||||
|
||||
<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 we’re going to split the *src/main.rs*
|
||||
file up and put some code into *src/lib.rs* so that we can test it and have a
|
||||
small `main` function.
|
||||
*src/main.rs* file with fewer responsibilities.
|
||||
|
||||
Let's move the following pieces of code from *src/main.rs* to a new file,
|
||||
*src/lib.rs*:
|
||||
Let’s move everything that isn't the `main` function from *src/main.rs* to a
|
||||
new file, *src/lib.rs*:
|
||||
|
||||
* The `run` function definition
|
||||
* The relevant `use` statements
|
||||
* The definition of `Config`
|
||||
* The `Config::new` function definition
|
||||
|
||||
The contents of *src/lib.rs* should now look like Listing 12-13:
|
||||
The contents of *src/lib.rs* should have the signatures shown in Listing 12-13
|
||||
(we've omitted the bodies of the functions for brevity):
|
||||
|
||||
<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>>{
|
||||
|
||||
We’ve made liberal use of `pub` here: on `Config`, its fields and its `new`
|
||||
method, and on the `run` function. We now have a library crate that has a
|
||||
public API that we can test.
|
||||
|
||||
#### Calling the Library Crate from the Binary Crate
|
||||
public API that we can test!
|
||||
|
||||
Now we need to bring the code we moved to *src/lib.rs* into the scope of the
|
||||
binary crate in *src/main.rs* by using `extern crate minigrep`. Then we’ll add a
|
||||
`use minigrep::Config` line to bring the `Config` type into scope, and prefix the
|
||||
`run` function with our crate name as shown in Listing 12-14:
|
||||
binary crate in *src/main.rs* by using `extern crate minigrep`. Then we’ll add
|
||||
a `use minigrep::Config` line to bring the `Config` type into scope, and prefix
|
||||
the `run` function with our crate name as shown in Listing 12-14:
|
||||
|
||||
<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 we’ll add a `use` `minigrep``::Config` line to bring the
|
||||
`Config` type into scope, and we'll prefix the `run` function with our crate
|
||||
name. With that, all the functionality should be connected and should work.
|
||||
Give it a `cargo run` and make sure everything is wired up correctly.
|
||||
|
||||
Whew! That was a lot of work, but we’ve set ourselves up for success in the
|
||||
future. Now it’s much easier to handle errors, and we’ve made our code more
|
||||
|
||||
@@ -10,21 +10,21 @@ In this section, we’re going to follow the Test Driven Development (TDD)
|
||||
process. This is a software development technique that follows this set of
|
||||
steps:
|
||||
|
||||
1. Write a test that fails, and run it to make sure it fails for the reason
|
||||
you expected.
|
||||
2. Write or modify just enough code to make the new test pass.
|
||||
3. Refactor the code you just added or changed, and make sure the tests
|
||||
continue to pass.
|
||||
4. Repeat!
|
||||
* Write a test that fails, and run it to make sure it fails for the reason you
|
||||
expected.
|
||||
* Write or modify just enough code to make the new test pass.
|
||||
* Refactor the code you just added or changed, and make sure the tests continue
|
||||
to pass.
|
||||
* Repeat!
|
||||
|
||||
This is just one of many ways to write software, but TDD can help drive the
|
||||
design of code. Writing the test before writing the code that makes the test
|
||||
design of code. Writing the test before you write the code that makes the test
|
||||
pass helps to maintain high test coverage throughout the process.
|
||||
|
||||
We're going to test drive the implementation of the part of our `minigrep`
|
||||
program that will actually do the searching for the query string in the file
|
||||
contents and produce a list of lines that match the query. We're going to add
|
||||
this functionality in a function called `search`.
|
||||
We’re going to test drive the implementation of the functionality that will
|
||||
actually do the searching for the query string in the file contents and produce
|
||||
a list of lines that match the query. We’re going to add this functionality in
|
||||
a function called `search`.
|
||||
|
||||
### Writing a Failing Test
|
||||
|
||||
@@ -65,23 +65,22 @@ Pick three.";
|
||||
<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 we’re
|
||||
searching is three lines, only one of which contains “duct”. We assert that
|
||||
the value returned from the `search` function contains only the line we expect.
|
||||
|
||||
We aren't able to run this test and watch it fail though, since this test
|
||||
doesn't even compile yet! We're going to add just enough code to get it to
|
||||
compile: a definition of the `search` function that always returns an empty
|
||||
vector, as shown in Listing 12-16. Once we have this, the test should compile
|
||||
and fail because an empty vector doesn't match a vector containing the one
|
||||
line `"safe, fast, productive."`.
|
||||
We aren’t able to run this test and watch it fail though, since this test
|
||||
doesn’t even compile–the search function doesn't exist yet! So now we’ll add
|
||||
just enough code to get the tests to compile and run: a definition of the
|
||||
`search` function that always returns an empty vector, as shown in Listing
|
||||
12-16. Once we have this, the test should compile and fail because an empty
|
||||
vector doesn’t match a vector containing the line `"safe, fast, productive."`.
|
||||
|
||||
<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. Let’s get the test to pass!
|
||||
|
||||
### Writing Code that Gets the Test to Pass
|
||||
### Writing Code to Pass the Test
|
||||
|
||||
Currently, our test is failing because we always return an empty vector. To fix
|
||||
that and implement `search`, our program needs to follow these steps:
|
||||
|
||||
1. Iterate through each line of the contents.
|
||||
2. Check if the line contains our query string.
|
||||
* If it does, add it to the list of values we're returning.
|
||||
* If it doesn't, do nothing.
|
||||
3. Return the list of results that match.
|
||||
* Iterate through each line of the contents.
|
||||
* Check if the line contains our query string.
|
||||
* If it does, add it to the list of values we’re returning.
|
||||
* If it doesn’t, do nothing.
|
||||
* Return the list of results that match.
|
||||
|
||||
Let’s take each step at a time, starting with iterating through lines.
|
||||
|
||||
@@ -175,7 +174,7 @@ conveniently named `lines`, that works as shown in Listing 12-17:
|
||||
<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 isn’t too bad, but it isn’t taking advantage of some useful
|
||||
features of iterators. We’ll be coming back to this example in Chapter 13 where
|
||||
we’ll explore iterators in detail and see how to improve it.
|
||||
|
||||
#### Using the `search` Function in the `run` Function
|
||||
|
||||
@@ -294,8 +281,8 @@ pub fn run(config: Config) -> Result<(), Box<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.
|
||||
We’re still using a `for` loop to get each line returned from `search` and
|
||||
printing out each line.
|
||||
|
||||
Now our whole program should be working! Let’s try it out, first with a word
|
||||
that should return exactly one line from the Emily Dickinson poem, “frog”:
|
||||
@@ -327,11 +314,11 @@ $ cargo run monomorphization poem.txt
|
||||
Running `target/debug/minigrep monomorphization poem.txt`
|
||||
```
|
||||
|
||||
Excellent! We've built our own version of a classic tool, and learned a lot
|
||||
about how to structure applications. We've also learned a bit about file input
|
||||
and output, lifetimes, testing, and command line parsing.
|
||||
Excellent! We’ve built our own mini version of a classic tool, and learned a
|
||||
lot about how to structure applications. We’ve also learned a bit about file
|
||||
input and output, lifetimes, testing, and command line parsing.
|
||||
|
||||
Feel free to move on to Chapter 13 if you'd like at this point. To round out
|
||||
this project chapter, though, we're going to briefly demonstrate how to work
|
||||
with environment variables and printing to standard error, both of which are
|
||||
useful when writing command line programs.
|
||||
To round out this project chapter, we’re going to briefly demonstrate how to
|
||||
work with environment variables and how to print to standard error, both of
|
||||
which are useful when writing command line programs. Feel free to move on to
|
||||
Chapter 13 if you’d like at this point.
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
## Working with Environment Variables
|
||||
|
||||
We're going to improve our tool with an extra feature: an option for case
|
||||
insensitive searching turned on via an environment variable. We could make this
|
||||
a command line option and require that users enter it each time they want it to
|
||||
apply, but instead we're going to use an environment variable. This allows our
|
||||
users to set the environment variable once and have all their searches be case
|
||||
insensitive in that terminal session.
|
||||
We’re going to improve our tool with an extra feature: an option for case
|
||||
insensitive searching that the user can turn on via an environment variable. We
|
||||
could make this a command line option and require that users enter it each time
|
||||
they want it to apply, but instead we’re going to use an environment variable.
|
||||
This allows our users to set the environment variable once and have all their
|
||||
searches be case insensitive in that terminal session.
|
||||
|
||||
### Writing a Failing Test for the Case-Insensitive `search` Function
|
||||
|
||||
First, let's add a new function that we will call when the environment variable
|
||||
is on.
|
||||
We want to add a new `search_case_insensitive` function that we will call when
|
||||
the environment variable is on.
|
||||
|
||||
We're going to continue following the TDD process that we started doing in the
|
||||
last section, and the first step is again to write a failing test. We'll add a
|
||||
new test for the new case insensitive search function, and rename our old test
|
||||
from `one_result` to `case_sensitive` to be clearer about the differences
|
||||
between the two tests, as shown in Listing 12-20:
|
||||
We’re going to continue following the TDD process, so the first step is again
|
||||
to write a failing test. We’ll add a new test for the new case-insensitive
|
||||
search function, and rename our old test from `one_result` to `case_sensitive`
|
||||
to be clearer about the differences between the two tests, as shown in Listing
|
||||
12-20:
|
||||
|
||||
<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 we’re 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 we’ve edited the old test’s `contents` too. We've added a new line
|
||||
with the text “Duct tape”, with a capital D, that shouldn’t match the query
|
||||
“duct” when we’re searching in a case sensitive manner. Changing the old test
|
||||
in this way helps ensure that we don’t accidentally break the case sensitive
|
||||
search functionality that we’ve already implemented; this test should pass now
|
||||
and should continue to pass as we work on the case insensitive search.
|
||||
|
||||
The new test for the case insensitive search uses "rUsT" with some capital
|
||||
letters as its query. The expected return value from the
|
||||
`search_case_insensitive` function we're going to add is that the query "rust"
|
||||
will match both the line containing "Rust:" with a capital R and also the line
|
||||
"Trust me." that contains "rust" with a lowercase r. This test will fail to
|
||||
compile right now since we haven't yet defined the `search_case_insensitive`
|
||||
function; feel free to add a skeleton implementation that always returns an
|
||||
empty vector in the same way that we did for the `search` function in Listing
|
||||
12-16 in order to see the test compile and fail.
|
||||
The new test for the case *insensitive* search uses “rUsT” as its query. In the
|
||||
`search_case_insensitive` function we’re going to add, the query “rUsT” should
|
||||
match both the line containing “Rust:” with a capital R and also the line
|
||||
“Trust me.” even though both of those have different casing than the query.
|
||||
This is our failing test, and it will fail to compile because we haven’t yet
|
||||
defined the `search_case_insensitive` function. Feel free to add a skeleton
|
||||
implementation that always returns an empty vector in the same way that we did
|
||||
for the `search` function in Listing 12-16 in order to see the test compile and
|
||||
fail.
|
||||
|
||||
### Implementing the `search_case_insensitive` Function
|
||||
|
||||
The `search_case_insensitive` function, shown in Listing 12-21, will be almost
|
||||
the same as the `search` function. The difference is that we'll lowercase the
|
||||
`query` function and each `line` so that whatever the case of the input
|
||||
arguments, they will be the same case when we check whether the line contains
|
||||
the query.
|
||||
the same as the `search` function. The only difference is that we’ll lowercase
|
||||
the `query` and each `line` so that whatever the case of the input arguments,
|
||||
they will be the same case when we check whether the line contains the query.
|
||||
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
@@ -111,42 +110,26 @@ matter if the user’s query is “rust”, “RUST”, “Rust”, or “rUsT
|
||||
the query as if it was “rust” and be insensitive to the case.
|
||||
|
||||
Note that `query` is now a `String` rather than a string slice, because calling
|
||||
`to_lowercase` is creating new data, not referencing existing data. If the
|
||||
query is "rUsT", that string slice does not contain a lowercase u or t for us
|
||||
to use, so we have to allocate a new `String` containing "rust". Because
|
||||
`query` is now a `String`, when we pass `query` as an argument to the
|
||||
`contains` method, we need to add an ampersand since the signature of
|
||||
`contains` is defined to take a string slice.
|
||||
`to_lowercase` creates new data rather than referencing existing data. Say the
|
||||
query is “rUsT”, as an example: that string slice does not contain a lowercase
|
||||
“u” or “t” for us to use, so we have to allocate a new `String` containing
|
||||
“rust”. When we pass `query` as an argument to the `contains` method now, we
|
||||
need to add an ampersand because the signature of `contains` is defined to take
|
||||
a string slice.
|
||||
|
||||
Next, we add a call to `to_lowercase` on each `line` before we check if it
|
||||
contains `query`. This will turn "Rust:" into "rust:" and "Trust me." into
|
||||
"trust me." Now that we've converted both `line` and `query` to all lowercase,
|
||||
we'll find matches no matter what case the text in the file has or the user
|
||||
entered in the query.
|
||||
contains `query` to lowercase all characters. Now that we’ve converted both
|
||||
`line` and `query` to lowercase, we’ll find matches no matter what the case of
|
||||
the query.
|
||||
|
||||
Let’s see if this implementation passes the tests:
|
||||
|
||||
```text
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
|
||||
Running target/debug/deps/minigrep-e58e9b12d35dc861
|
||||
|
||||
running 2 tests
|
||||
test test::case_insensitive ... ok
|
||||
test test::case_sensitive ... ok
|
||||
|
||||
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
|
||||
|
||||
Running target/debug/minigrep-8a7faa2662b5030a
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||
|
||||
Doc-tests minigrep
|
||||
|
||||
running 0 tests
|
||||
|
||||
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
|
||||
```
|
||||
|
||||
Great! Now, let’s actually call the new `search_case_insensitive` function from
|
||||
@@ -215,10 +198,10 @@ pub fn run(config: Config) -> Result<(), Box<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 we’re going to use the `var` method from the
|
||||
`env` module to check for an environment variable named `CASE_INSENSITIVE`, as
|
||||
shown in Listing 12-23:
|
||||
|
||||
<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:
|
||||
We’re using the `is_err` method on the `Result` to check to see if it’s an
|
||||
error, and therefore unset, which means it *should* do a case sensitive search.
|
||||
If the `CASE_INSENSITIVE` environment variable is set to anything, `is_err`
|
||||
will return false and it will perform a case insensitive search. We don’t care
|
||||
about the *value* of the environment variable, just whether it’s set or unset,
|
||||
so we’re checking `is_err` rather than `unwrap`, `expect`, or any of the other
|
||||
methods we’ve seen on `Result`.
|
||||
|
||||
We pass the value in the `case_sensitive` variable to the `Config` instance so
|
||||
that the `run` function can read that value and decide whether to call `search`
|
||||
or `search_case_insensitive` as we implemented in Listing 12-22.
|
||||
|
||||
Let’s give it a try! First, we’ll run our program without the environment
|
||||
variable set and with the query “to”, which should match any line that contains
|
||||
the word “to” in all lowercase:
|
||||
|
||||
```text
|
||||
$ cargo run to poem.txt
|
||||
@@ -283,9 +265,9 @@ Are you nobody, too?
|
||||
How dreary to be somebody!
|
||||
```
|
||||
|
||||
Looks like that still works! Now, let's run the program with `CASE_INSENSITIVE`
|
||||
set to 1 but with the same query "to", and we should get lines that contain
|
||||
"to" that might have capital letters:
|
||||
Looks like that still works! Now, let’s run the program with `CASE_INSENSITIVE`
|
||||
set to 1 but with the same query “to”, and we should get lines that contain
|
||||
“to” that might have uppercase letters:
|
||||
|
||||
```text
|
||||
$ CASE_INSENSITIVE=1 cargo run to poem.txt
|
||||
@@ -310,4 +292,4 @@ environment variable, and decide which should take precedence if the program is
|
||||
run with contradictory values.
|
||||
|
||||
The `std::env` module contains many more useful features for dealing with
|
||||
environment variables; check out its documentation to see what's available.
|
||||
environment variables; check out its documentation to see what’s available.
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
## Write to `stderr` Instead of `stdout`
|
||||
## Writing Error Messages to `stderr` Instead of `stdout`
|
||||
|
||||
Right now, we're writing all of our output to the terminal with `println!`.
|
||||
Most terminals provide two kinds of output: "standard out" for general
|
||||
information, and "standard error" for error messages. This distinction is the
|
||||
behavior that's expected of command line programs: it enables users to choose
|
||||
to direct a program's successful output to a file but still print error
|
||||
messages to the screen, for example. `println!` is only capable of printing to
|
||||
standard out, though, so we have to use something else in order to print to
|
||||
standard error.
|
||||
At the moment we’re writing all of our output to the terminal with the
|
||||
`println!` function. Most terminals provide two kinds of output: *standard out*
|
||||
for general information, and *standard error* for error messages. This
|
||||
distinction enables users to choose whether to direct a the successful output
|
||||
of a program to a file but still print error messages to the screen.
|
||||
|
||||
We can verify that, the way we've written `minigrep` so far, everything is being
|
||||
written to standard out, including error messages that should be written to
|
||||
standard error instead. We'll do that by intentionally causing an error, the
|
||||
one that happens when we run the program without any arguments. We're going to
|
||||
redirect standard output to a file, but not standard error. The way command
|
||||
line programs are expected to work is that, because the output is an error
|
||||
message, it should be shown on the screen rather than being redirected to the
|
||||
file. Let's see that our program is not currently meeting this expectation by
|
||||
using `>` and specifying a filename, *output.txt*, that we want to redirect
|
||||
standard out to:
|
||||
The `println!` function is only capable of printing to standard out, though, so
|
||||
we have to use something else in order to print to standard error.
|
||||
|
||||
### Checking Where Errors are Written to
|
||||
|
||||
First, let’s observe how all content printed by `minigrep` is currently being
|
||||
written to standard out, including error messages that we want to write to
|
||||
standard error instead. We’ll do that by redirecting the standard output stream
|
||||
to a file while we also intentionally cause an error. We won't redirect the
|
||||
standard error stream, so any content sent to standard error will continue to
|
||||
display on the screen. Command line programs are expected to send error
|
||||
messages to the standard error stream so that we can still see error messages
|
||||
on the screen even if we choose to redirect the standard output stream to a
|
||||
file. Our program is not currently well-behaved; we're about to see that it
|
||||
saves the error message output to the file instead!
|
||||
|
||||
The way to demonstrate this behavior is by running the program with `>` and the
|
||||
filename, *output.txt*, that we want to redirect the standard output stream to.
|
||||
We're not going to pass any arguments, which should cause an error:
|
||||
|
||||
```text
|
||||
$ cargo run > output.txt
|
||||
@@ -33,75 +39,55 @@ file. Let’s see what *output.txt* contains:
|
||||
Problem parsing arguments: not enough arguments
|
||||
```
|
||||
|
||||
Yup, there's our error message, which means it's being printed to standard out.
|
||||
This isn't what's expected from command line programs. It's much more useful
|
||||
for error messages like this to be printed to standard error, and only have
|
||||
data printed to standard out from a successful run end up in the file when we
|
||||
redirect standard out in this way. Let's change how error messages are printed
|
||||
as shown in Listing 12-23. Because of the refactoring we did earlier in this
|
||||
chapter, all of the code that prints error messages is in one place, in `main`:
|
||||
Yup, our error message is being printed to standard out. It’s much more useful
|
||||
for error messages like this to be printed to standard error, and have only
|
||||
data from a successful run end up in the file when we redirect standard out in
|
||||
this way. We’ll change that.
|
||||
|
||||
### Printing Errors to Standard Error
|
||||
|
||||
Let’s change how error messages are printed using the code in Listing 12-24.
|
||||
Because of the refactoring we did earlier in this chapter, all the code that
|
||||
prints error messages is in one function, in `main`. The standard library
|
||||
provides the `eprintln!` macro that prints to the standard error stream, so
|
||||
let's change the two places we were calling `println!` to print errors so that
|
||||
these spots use `eprintln!` instead:
|
||||
|
||||
<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!`, let’s try running the program again
|
||||
in the same way, without any arguments and redirecting `stdout` with `>`:
|
||||
|
||||
```text
|
||||
$ cargo run > output.txt
|
||||
Problem parsing arguments: not enough arguments
|
||||
```
|
||||
|
||||
Now we see our error on the screen, and `output.txt` contains nothing, which is
|
||||
the behavior that's expected of command line programs.
|
||||
Now we see our error on the screen and `output.txt` contains nothing, which is
|
||||
the behavior expected of command line programs.
|
||||
|
||||
If we run the program again with arguments that don't cause an error, but still
|
||||
redirecting standard out to a file:
|
||||
If we run the program again with arguments that don’t cause an error, but still
|
||||
redirect standard out to a file:
|
||||
|
||||
```text
|
||||
$ cargo run to poem.txt > output.txt
|
||||
@@ -117,15 +103,15 @@ Are you nobody, too?
|
||||
How dreary to be somebody!
|
||||
```
|
||||
|
||||
This demonstrates that we're now using standard out for successful output and
|
||||
standard error for error output as appropriate.
|
||||
This demonstrates that we’re now using `stdout` for successful output and
|
||||
`stderr` for error output as appropriate.
|
||||
|
||||
## Summary
|
||||
|
||||
In this chapter, we’ve recapped on some of the major concepts so far and
|
||||
covered how to do common I/O operations in a Rust context. By using command
|
||||
line arguments, files, environment variables, and the `writeln!` macro with
|
||||
`stderr`, you're now prepared to write command line applications. By using the
|
||||
line arguments, files, environment variables, and the `eprintln!` macro with
|
||||
`stderr`, you’re now prepared to write command line applications. By using the
|
||||
concepts from previous chapters, your code will be well-organized, be able to
|
||||
store data effectively in the appropriate data structures, handle errors
|
||||
nicely, and be well tested.
|
||||
|
||||
Reference in New Issue
Block a user