@ -340,17 +340,17 @@ Let’s address these four problems by refactoring our project.
@@ -340,17 +340,17 @@ Let’s address these four problems by refactoring our project.
### Separation of Concerns for Binary Projects
The organizational problem of allocating responsibility for multiple tasks to
the `main` function is common to many binary projects. As a result, the Rust
community has developed guidelines for splitting the separate concerns of a
binary program when `main` starts getting large. This process has the following
steps:
the `main` function is common to many binary projects. As a result, many Rust
programmers find it useful to split up the separate concerns of a binary
program when the`main`function starts getting large. This process has the
following steps:
* Split your program into a *main.rs* file and a *lib.rs* file and move your
program’s logic to *lib.rs*.
* As long as your command line parsing logic is small, it can remain in
*main.rs*.
the `main` function.
* When the command line parsing logic starts getting complicated, extract it
from *main.rs* and move it to *lib.rs*.
from the `main` function into other functions or types.
The responsibilities that remain in the `main` function after this process
should be limited to the following:
@ -363,16 +363,15 @@ should be limited to the following:
@@ -363,16 +363,15 @@ should be limited to the following:
This pattern is about separating concerns: *main.rs* handles running the
program and *lib.rs* handles all the logic of the task at hand. Because you
can’t test the `main` function directly, this structure lets you test all of
your program’s logic by moving it into functions in *lib.rs*. The code that
remains in *main.rs* will be small enough to verify its correctness by reading
it. Let’s rework our program by following this process.
your program’s logic by moving it out of the `main` function. The code that
remains in the `main` function will be small enough to verify its correctness
by reading it. Let’s rework our program by following this process.
#### Extracting the Argument Parser
We’ll extract the functionality for parsing arguments into a function that
`main` will call to prepare for moving the command line parsing logic to
*src/lib.rs*. Listing 12-5 shows the new start of `main` that calls a new
function `parse_config`, which we’ll define in *src/main.rs* for the moment.
`main` will call. Listing 12-5 shows the new start of the `main` function that
calls a new function `parse_config`, which we’ll define in *src/main.rs*.
src/main.rs
@ -560,6 +559,7 @@ $ cargo run
@@ -560,6 +559,7 @@ $ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep`
thread 'main' panicked at src/main.rs:26:13:
not enough arguments
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
@ -731,19 +732,22 @@ Problem parsing arguments: not enough arguments
@@ -731,19 +732,22 @@ Problem parsing arguments: not enough arguments
Great! This output is much friendlier for our users.
### Extracting Logic from main
<!-- Old headings. Do not remove or links may break. -->
<aid="extracting-logic-from-main"></a>
### Extracting Logic from the main Function
Now that we’ve finished refactoring the configuration parsing, let’s turn to
the program’s logic. As we stated in “Separation of Concerns for Binary
Projects”, we’ll
extract a function named `run` that will hold all the logic currently in the
`main` function that isn’t involved with setting up configuration or handling
errors. When we’re done, `main` will be concise and easy to verify by
inspection, and we’ll be able to write tests for all the other logic.
errors. When we’re done, the `main`function will be concise and easy to verify
by inspection, and we’ll be able to write tests for all the other logic.
Listing 12-11 shows the extracted `run` function. For now, we’re just making
the small, incremental improvement of extracting the function. We’re still
defining the function in *src/main.rs*.
Listing 12-11 shows the small, incremental improvement of extracting a `run`
function.
src/main.rs
@ -905,71 +909,67 @@ Our `minigrep` project is looking good so far! Now we’ll split the
@@ -905,71 +909,67 @@ Our `minigrep` project is looking good so far! Now we’ll split the
*src/main.rs* file and put some code into the *src/lib.rs* file. That way, we
can test the code and have a *src/main.rs* file with fewer responsibilities.
Let’s move all the code that isn’t in the `main` function from *src/main.rs* to
*src/lib.rs*:
* The `run` function definition
* The relevant `use` statements
* The definition of `Config`
* The `Config::build` function definition
Let’s define the code responsible for searching text in *src/lib.rs* rather
than in *src/main.rs*, which will let us (or anyone else using our
`minigrep` library) call the searching function from more contexts than our
`minigrep` binary.
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). Note that this won’t
compile until we modify *src/main.rs* in Listing 12-14.
First, let’s define the `search` function signature in *src/lib.rs* as shown in
Listing 12-13, with a body that calls the `unimplemented!` macro. We’ll explain
the signature in more detail when we fill in the implementation.
let contents = fs::read_to_string(config.file_path)?;
for line in search(&config.query, &contents) {
println!("{line}");
}
Ok(())
}
```
Listing 12-14: Using the `minigrep` library crate in *src/main.rs*
Listing 12-14: Using the `minigrep` library crate’s `search` function in *src/main.rs*
We add a `use minigrep::Config` line to bring the `Config` type from the
library crate into the binary crate’s scope, and we prefix the `run` function
with our crate name. Now all the functionality should be connected and should
work. Run the program with `cargo run` and make sure everything works correctly.
We add a `use minigrep::search` line to bring the `search` function from
the library crate into the binary crate’s scope. Then, in the `run` function,
rather than printing out the contents of the file, we call the `search`
function and pass the `config.query` value and `contents` as arguments. Then
`run` will use a `for` loop to print each line returned from `search` that
matched the query. This is also a good time to remove the `println!` calls in
the `main` function that displayed the query and the file path so that our
program only prints the search results (if no errors occur).
Note that the search function will be collecting all the results into a vector
it returns before any printing happens. This implementation could be slow to
display results when searching large files because results aren’t printed as
they’re found; we’ll discuss a possible way to fix this using iterators in
Chapter 13.
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 the code more
@ -981,11 +981,10 @@ write some tests!
@@ -981,11 +981,10 @@ write some tests!
## Developing the Library’s Functionality with Test-Driven Development
Now that we’ve extracted the logic into *src/lib.rs* and left the argument
collecting and error handling in *src/main.rs*, it’s much easier to write tests
for the core functionality of our code. We can call functions directly with
various arguments and check return values without having to call our binary
from the command line.
Now that we have the search logic in *src/lib.rs* separate from the `main`
function, it’s much easier to write tests for the core functionality of our
code. We can call functions directly with various arguments and check return
values without having to call our binary from the command line.
In this section, we’ll add the searching logic to the `minigrep` program using
the test-driven development (TDD) process with the following steps:
@ -1008,17 +1007,17 @@ lines that match the query. We’ll add this functionality in a function called
@@ -1008,17 +1007,17 @@ lines that match the query. We’ll add this functionality in a function called
### Writing a Failing Test
Because we don’t need them anymore, let’s remove the `println!` statements from
*src/lib.rs* and *src/main.rs* that we used to check the program’s behavior.
Then, in *src/lib.rs*, we’ll add a `tests` module with a test function, as we
did in Chapter 11. The test function specifies
the behavior we want the `search` function to have: it will take a query and
the text to search, and it will return only the lines from the text that
contain the query. Listing 12-15 shows this test, which won’t compile yet.
In *src/lib.rs*, we’ll add a `tests` module with a test function, as we did in
Chapter 11. The test function specifies the
behavior we want the `search` function to have: it will take a query and the
text to search, and it will return only the lines from the text that contain
Listing 12-15: Creating a failing test for the `search` function we wish we had
Listing 12-15: Creating a failing test for the `search` function for the functionality we wish we had
This test searches for the string `"duct"`. The text we’re searching is three
lines, only one of which contains `"duct"` (note that the backslash after the
@ -1044,11 +1043,11 @@ opening double quote tells Rust not to put a newline character at the beginning
@@ -1044,11 +1043,11 @@ opening double quote tells Rust not to put a newline character at the beginning
of the contents of this string literal). We assert that the value returned from
the `search` function contains only the line we expect.
We aren’t yet able to run this test and watch it fail because the test doesn’t
even compile: the `search` function doesn’t exist yet! In accordance with TDD
principles, we’ll add just enough code to get the test to compile and run by
adding a definition of the `search` function that always returns an empty
vector, as shown in Listing 12-16. Then the test should compile and fail
If we run this test, it will currently fail because the `unimplemented!` macro
panics with the message “not implemented”. In accordance with TDD principles,
we’ll take a small step of adding just enough code to get the test to not panic
when calling the function by defining the `search` function to always return an
empty vector, as shown in Listing 12-16. Then the test should compile and fail
because an empty vector doesn’t match a vector containing the line `"safe, fast, productive."`
For more information about this error, try `rustc --explain E0106`.
error: could not compile `minigrep` (lib) due to 1 previous error
```
Rust can’t possibly know which of the two arguments we need, so we need to tell
it explicitly. Because `contents` is the argument that contains all of our text
Rust can’t know which of the two parameters we need for the output, so we need
to tell it explicitly. Note that the help text suggests specifying the same
lifetime parameter for all the parameters and the output type, which is
incorrect! Because `contents` is the parameter that contains all of our text
and we want to return the parts of that text that match, we know `contents` is
the argument that should be connected to the return value using the lifetime
syntax.
the only parameter that should be connected to the return value using the
lifetime syntax.
Other programming languages don’t require you to connect arguments to return
values in the signature, but this practice will get easier over time. You might
@ -1110,37 +1111,6 @@ want to compare this example with the examples in the “Validating References
@@ -1110,37 +1111,6 @@ want to compare this example with the examples in the “Validating References
thread 'tests::one_result' panicked at src/lib.rs:44:9:
assertion `left == right` failed
left: ["safe, fast, productive."]
right: []
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::one_result
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
```
Great, the test fails, exactly as we expected. Let’s get the test to pass!
### Writing Code to Pass the Test
Currently, our test is failing because we always return an empty vector. To fix
@ -1264,29 +1234,6 @@ but it doesn’t take advantage of some useful features of iterators. We’ll
@@ -1264,29 +1234,6 @@ but it doesn’t take advantage of some useful features of iterators. We’ll
return to this example in Chapter 13, where
we’ll explore iterators in detail, and look at how to improve it.
#### Using the search Function in the run Function
Now that the `search` function is working and tested, we need to call `search`
from our `run` function. We need to pass the `config.query` value and the
`contents` that `run` reads from the file to the `search` function. Then `run`
let contents = fs::read_to_string(config.file_path)?;
for line in search(&config.query, &contents) {
println!("{line}");
}
Ok(())
}
```
We’re still using a `for` loop to return each line from `search` and print it.
Now the entire program should work! Let’s try it out, first with a word that
should return exactly one line from the Emily Dickinson poem: *frog*.
@ -1330,7 +1277,7 @@ useful when you’re writing command line programs.
@@ -1330,7 +1277,7 @@ useful when you’re writing command line programs.
## Working with Environment Variables
We’ll improve `minigrep` by adding an extra feature: an option for
We’ll improve the `minigrep` binary by adding an extra feature: an option for
case-insensitive searching that the user can turn on via an environment
variable. We could make this feature a command line option and require that
users enter it each time they want it to apply, but by instead making it an
@ -1339,12 +1286,12 @@ and have all their searches be case insensitive in that terminal session.
@@ -1339,12 +1286,12 @@ and have all their searches be case insensitive in that terminal session.
### Writing a Failing Test for the Case-Insensitive search Function
We first add a new `search_case_insensitive` function that will be called when
the environment variable has a value. We’ll continue to follow the TDD process,
so the first step is again to write a failing test. We’ll add a new test for
the new `search_case_insensitive` function and rename our old test from
`one_result` to `case_sensitive` to clarify the differences between the two
tests, as shown in Listing 12-20.
We first add a new `search_case_insensitive` function to the `minigrep` library
that will be called when the environment variable has a value. We’ll continue
to follow the TDD process, so the first step is again to write a failing test.
We’ll add a new test for the new `search_case_insensitive` function and rename
our old test from `one_result` to `case_sensitive` to clarify the differences
Listing 12-21: Defining the `search_case_insensitive` function to lowercase the query and the line before comparing them
First we lowercase the `query` string and store it in a new variable with the
same name, shadowing the original. Calling `to_lowercase` on the query is
necessary so that no matter whether the user’s query is `"rust"`, `"RUST"`,
`"Rust"`, or `"rUsT"`, we’ll treat the query as if it were `"rust"` and be
same name, shadowing the original`query`. Calling `to_lowercase` on the query
is necessary so that no matter whether the user’s query is `"rust"`, `"RUST"`,
`"Rust"`, or ```"``rUsT``"```, we’ll treat the query as if it were `"rust"` and be
insensitive to the case. While `to_lowercase` will handle basic Unicode, it
won’t be 100% accurate. If we were writing a real application, we’d want to do a
bit more work here, but this section is about environment variables, not
Unicode, so we’ll leave it at that here.
won’t be 100 percent accurate. If we were writing a real application, we’d want
to do a bit more work here, but this section is about environment variables,
not Unicode, so we’ll leave it at that here.
Note that `query` is now a `String` rather than a string slice because calling
`to_lowercase` creates new data rather than referencing existing data. Say the
@ -1484,7 +1431,7 @@ struct to switch between case-sensitive and case-insensitive search. Adding
@@ -1484,7 +1431,7 @@ struct to switch between case-sensitive and case-insensitive search. Adding
this field will cause compiler errors because we aren’t initializing this field
anywhere yet:
Filename: src/lib.rs
Filename: src/main.rs
```
pub struct Config {
@ -1499,10 +1446,14 @@ function to check the `ignore_case` field’s value and use that to decide
@@ -1499,10 +1446,14 @@ function to check the `ignore_case` field’s value and use that to decide
whether to call the `search` function or the `search_case_insensitive`
function, as shown in Listing 12-22. This still won’t compile yet.
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
@ -1523,19 +1474,15 @@ Listing 12-22: Calling either `search` or `search_case_insensitive` based on the
@@ -1523,19 +1474,15 @@ Listing 12-22: Calling either `search` or `search_case_insensitive` based on the
Finally, we need to check for the environment variable. The functions for
working with environment variables are in the `env` module in the standard
library, so we bring that module into scope at the top of *src/lib.rs*. Then
we’ll use the `var` function from the `env` module to check to see if any value
has been set for an environment variable named `IGNORE_CASE`, as shown in
Listing 12-23.
library, which is already in scope at the top of *src/main.rs*. We’ll use the
`var` function from the `env` module to check to see if any value has been set
for an environment variable named `IGNORE_CASE`, as shown in Listing 12-23.
@ -36,17 +36,17 @@ Let’s address these four problems by refactoring our project.
@@ -36,17 +36,17 @@ Let’s address these four problems by refactoring our project.
### Separation of Concerns for Binary Projects
The organizational problem of allocating responsibility for multiple tasks to
the `main` function is common to many binary projects. As a result, the Rust
community has developed guidelines for splitting the separate concerns of a
binary program when `main` starts getting large. This process has the following
steps:
the `main` function is common to many binary projects. As a result, many Rust
programmers find it useful to split up the separate concerns of a binary
program when the`main`function starts getting large. This process has the
following steps:
- Split your program into a _main.rs_ file and a _lib.rs_ file and move your
program’s logic to _lib.rs_.
- As long as your command line parsing logic is small, it can remain in
_main.rs_.
the `main` function.
- When the command line parsing logic starts getting complicated, extract it
from _main.rs_ and move it to _lib.rs_.
from the `main` function into other functions or types.
The responsibilities that remain in the `main` function after this process
should be limited to the following:
@ -59,16 +59,15 @@ should be limited to the following:
@@ -59,16 +59,15 @@ should be limited to the following:
This pattern is about separating concerns: _main.rs_ handles running the
program and _lib.rs_ handles all the logic of the task at hand. Because you
can’t test the `main` function directly, this structure lets you test all of
your program’s logic by moving it into functions in _lib.rs_. The code that
remains in _main.rs_ will be small enough to verify its correctness by reading
it. Let’s rework our program by following this process.
your program’s logic by moving it out of the `main` function. The code that
remains in the `main` function will be small enough to verify its correctness
by reading it. Let’s rework our program by following this process.
#### Extracting the Argument Parser
We’ll extract the functionality for parsing arguments into a function that
`main` will call to prepare for moving the command line parsing logic to
_src/lib.rs_. Listing 12-5 shows the new start of `main` that calls a new
function `parse_config`, which we’ll define in _src/main.rs_ for the moment.
`main` will call. Listing 12-5 shows the new start of the `main` function that
calls a new function `parse_config`, which we’ll define in _src/main.rs_.
<Listingnumber="12-5"file-name="src/main.rs"caption="Extracting a `parse_config` function from `main`">
@ -332,19 +331,22 @@ extra output. Let’s try it:
@@ -332,19 +331,22 @@ extra output. Let’s try it:
Great! This output is much friendlier for our users.
### Extracting Logic from `main`
<!-- Old headings. Do not remove or links may break. -->
<aid="extracting-logic-from-main"></a>
### Extracting Logic from the `main` Function
Now that we’ve finished refactoring the configuration parsing, let’s turn to
the program’s logic. As we stated in [“Separation of Concerns for Binary
extract a function named `run` that will hold all the logic currently in the
`main` function that isn’t involved with setting up configuration or handling
errors. When we’re done, `main` will be concise and easy to verify by
inspection, and we’ll be able to write tests for all the other logic.
errors. When we’re done, the `main`function will be concise and easy to verify
by inspection, and we’ll be able to write tests for all the other logic.
Listing 12-11 shows the extracted `run` function. For now, we’re just making
the small, incremental improvement of extracting the function. We’re still
defining the function in _src/main.rs_.
Listing 12-11 shows the small, incremental improvement of extracting a `run`
function.
<Listingnumber="12-11"file-name="src/main.rs"caption="Extracting a `run` function containing the rest of the program logic">
@ -440,34 +442,31 @@ Our `minigrep` project is looking good so far! Now we’ll split the
@@ -440,34 +442,31 @@ Our `minigrep` project is looking good so far! Now we’ll split the
_src/main.rs_ file and put some code into the _src/lib.rs_ file. That way, we
can test the code and have a _src/main.rs_ file with fewer responsibilities.
Let’s move all the code that isn’t in the `main` function from _src/main.rs_ to
_src/lib.rs_:
- The `run` function definition
- The relevant `use` statements
- The definition of `Config`
- The `Config::build` function definition
Let’s define the code responsible for searching text in _src/lib.rs_ rather
than in _src/main.rs_, which will let us (or anyone else using our
`minigrep` library) call the searching function from more contexts than our
`minigrep` binary.
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). Note that this won’t
compile until we modify _src/main.rs_ in Listing 12-14.
First, let’s define the `search` function signature in _src/lib.rs_ as shown in
Listing 12-13, with a body that calls the `unimplemented!` macro. We’ll explain
the signature in more detail when we fill in the implementation.
<Listingnumber="12-13"file-name="src/lib.rs"caption="Moving `Config` and `run` into*src/lib.rs*">
<Listingnumber="12-13"file-name="src/lib.rs"caption="Defining the `search` function in *src/lib.rs*">
@ -475,10 +474,20 @@ binary crate in _src/main.rs_, as shown in Listing 12-14.
@@ -475,10 +474,20 @@ binary crate in _src/main.rs_, as shown in Listing 12-14.
</Listing>
We add a `use minigrep::Config` line to bring the `Config` type from the
library crate into the binary crate’s scope, and we prefix the `run` function
with our crate name. Now all the functionality should be connected and should
work. Run the program with `cargo run` and make sure everything works correctly.
We add a `use minigrep::search` line to bring the `search` function from
the library crate into the binary crate’s scope. Then, in the `run` function,
rather than printing out the contents of the file, we call the `search`
function and pass the `config.query` value and `contents` as arguments. Then
`run` will use a `for` loop to print each line returned from `search` that
matched the query. This is also a good time to remove the `println!` calls in
the `main` function that displayed the query and the file path so that our
program only prints the search results (if no errors occur).
Note that the search function will be collecting all the results into a vector
it returns before any printing happens. This implementation could be slow to
display results when searching large files because results aren’t printed as
they’re found; we’ll discuss a possible way to fix this using iterators in
Chapter 13.
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 the code more
## Developing the Library’s Functionality with Test-Driven Development
Now that we’ve extracted the logic into _src/lib.rs_ and left the argument
collecting and error handling in _src/main.rs_, it’s much easier to write tests
for the core functionality of our code. We can call functions directly with
various arguments and check return values without having to call our binary
from the command line.
Now that we have the search logic in _src/lib.rs_ separate from the `main`
function, it’s much easier to write tests for the core functionality of our
code. We can call functions directly with various arguments and check return
values without having to call our binary from the command line.
In this section, we’ll add the searching logic to the `minigrep` program using
the test-driven development (TDD) process with the following steps:
@ -27,15 +26,13 @@ lines that match the query. We’ll add this functionality in a function called
@@ -27,15 +26,13 @@ lines that match the query. We’ll add this functionality in a function called
### Writing a Failing Test
Because we don’t need them anymore, let’s remove the `println!` statements from
_src/lib.rs_ and _src/main.rs_ that we used to check the program’s behavior.
Then, in _src/lib.rs_, we’ll add a `tests` module with a test function, as we
did in [Chapter 11][ch11-anatomy]<!-- ignore -->. The test function specifies
the behavior we want the `search` function to have: it will take a query and
the text to search, and it will return only the lines from the text that
contain the query. Listing 12-15 shows this test, which won’t compile yet.
In _src/lib.rs_, we’ll add a `tests` module with a test function, as we did in
[Chapter 11][ch11-anatomy]<!-- ignore -->. The test function specifies the
behavior we want the `search` function to have: it will take a query and the
text to search, and it will return only the lines from the text that contain
the query. Listing 12-15 shows this test.
<Listingnumber="12-15"file-name="src/lib.rs"caption="Creating a failing test for the `search` function we wish we had">
<Listingnumber="12-15"file-name="src/lib.rs"caption="Creating a failing test for the `search` function for the functionality we wish we had">
@ -49,15 +46,15 @@ opening double quote tells Rust not to put a newline character at the beginning
@@ -49,15 +46,15 @@ opening double quote tells Rust not to put a newline character at the beginning
of the contents of this string literal). We assert that the value returned from
the `search` function contains only the line we expect.
We aren’t yet able to run this test and watch it fail because the test doesn’t
even compile: the `search` function doesn’t exist yet! In accordance with TDD
principles, we’ll add just enough code to get the test to compile and run by
adding a definition of the `search` function that always returns an empty
vector, as shown in Listing 12-16. Then the test should compile and fail
If we run this test, it will currently fail because the `unimplemented!` macro
panics with the message “not implemented”. In accordance with TDD principles,
we’ll take a small step of adding just enough code to get the test to not panic
when calling the function by defining the `search` function to always return an
empty vector, as shown in Listing 12-16. Then the test should compile and fail
because an empty vector doesn’t match a vector containing the line `"safe,
fast, productive."`
<Listingnumber="12-16"file-name="src/lib.rs"caption="Defining just enough of the `search` function so our test will compile">
<Listingnumber="12-16"file-name="src/lib.rs"caption="Defining just enough of the `search` function so calling it won’t panic">
Rust can’t possibly know which of the two arguments we need, so we need to tell
it explicitly. Because `contents` is the argument that contains all of our text
Rust can’t know which of the two parameters we need for the output, so we need
to tell it explicitly. Note that the help text suggests specifying the same
lifetime parameter for all the parameters and the output type, which is
incorrect! Because `contents` is the parameter that contains all of our text
and we want to return the parts of that text that match, we know `contents` is
the argument that should be connected to the return value using the lifetime
syntax.
the only parameter that should be connected to the return value using the
lifetime syntax.
Other programming languages don’t require you to connect arguments to return
values in the signature, but this practice will get easier over time. You might
@ -99,14 +98,6 @@ want to compare this example with the examples in the [“Validating References
@@ -99,14 +98,6 @@ want to compare this example with the examples in the [“Validating References
with Lifetimes”][validating-references-with-lifetimes]<!-- ignore --> section
Great, the test fails, exactly as we expected. Let’s get the test to pass!
### Writing Code to Pass the Test
Currently, our test is failing because we always return an empty vector. To fix
@ -189,21 +180,6 @@ but it doesn’t take advantage of some useful features of iterators. We’ll
@@ -189,21 +180,6 @@ but it doesn’t take advantage of some useful features of iterators. We’ll
return to this example in [Chapter 13][ch13-iterators]<!-- ignore -->, where
we’ll explore iterators in detail, and look at how to improve it.
#### Using the `search` Function in the `run` Function
Now that the `search` function is working and tested, we need to call `search`
from our `run` function. We need to pass the `config.query` value and the
`contents` that `run` reads from the file to the `search` function. Then `run`
We’ll improve `minigrep` by adding an extra feature: an option for
We’ll improve the `minigrep` binary by adding an extra feature: an option for
case-insensitive searching that the user can turn on via an environment
variable. We could make this feature a command line option and require that
users enter it each time they want it to apply, but by instead making it an
@ -9,12 +9,12 @@ and have all their searches be case insensitive in that terminal session.
@@ -9,12 +9,12 @@ and have all their searches be case insensitive in that terminal session.
### Writing a Failing Test for the Case-Insensitive `search` Function
We first add a new `search_case_insensitive` function that will be called when
the environment variable has a value. We’ll continue to follow the TDD process,
so the first step is again to write a failing test. We’ll add a new test for
the new `search_case_insensitive` function and rename our old test from
`one_result` to `case_sensitive` to clarify the differences between the two
tests, as shown in Listing 12-20.
We first add a new `search_case_insensitive` function to the `minigrep` library
that will be called when the environment variable has a value. We’ll continue
to follow the TDD process, so the first step is again to write a failing test.
We’ll add a new test for the new `search_case_insensitive` function and rename
our old test from `one_result` to `case_sensitive` to clarify the differences
between the two tests, as shown in Listing 12-20.
<Listingnumber="12-20"file-name="src/lib.rs"caption="Adding a new failing test for the case-insensitive function we’re about to add">
@ -58,11 +58,11 @@ they’ll be the same case when we check whether the line contains the query.
@@ -58,11 +58,11 @@ they’ll be the same case when we check whether the line contains the query.
First we lowercase the `query` string and store it in a new variable with the
same name, shadowing the original `query`. Calling `to_lowercase` on the query
is necessary so that no matter whether the user’s query is `"rust"`, `"RUST"`,
`"Rust"`, or `"rUsT"`, we’ll treat the query as if it were `"rust"` and be
`"Rust"`, or `"``rUsT``"`, we’ll treat the query as if it were `"rust"` and be
insensitive to the case. While `to_lowercase` will handle basic Unicode, it
won’t be 100% accurate. If we were writing a real application, we’d want to do a
bit more work here, but this section is about environment variables, not
Unicode, so we’ll leave it at that here.
won’t be 100 percent accurate. If we were writing a real application, we’d want
to do a bit more work here, but this section is about environment variables,
not Unicode, so we’ll leave it at that here.
Note that `query` is now a `String` rather than a string slice because calling
`to_lowercase` creates new data rather than referencing existing data. Say the
@ -88,10 +88,10 @@ struct to switch between case-sensitive and case-insensitive search. Adding
@@ -88,10 +88,10 @@ struct to switch between case-sensitive and case-insensitive search. Adding
this field will cause compiler errors because we aren’t initializing this field
We added the `ignore_case` field that holds a Boolean. Next, we need the `run`
@ -99,25 +99,24 @@ function to check the `ignore_case` field’s value and use that to decide
@@ -99,25 +99,24 @@ function to check the `ignore_case` field’s value and use that to decide
whether to call the `search` function or the `search_case_insensitive`
function, as shown in Listing 12-22. This still won’t compile yet.
<Listingnumber="12-22"file-name="src/lib.rs"caption="Calling either `search` or `search_case_insensitive` based on the value in `config.ignore_case`">
<Listingnumber="12-22"file-name="src/main.rs"caption="Calling either `search` or `search_case_insensitive` based on the value in `config.ignore_case`">