Merge pull request #627 from lslima91/chapter13-io_project

renamed chapter13 io_project code to be equal to chapter 12
This commit is contained in:
Carol (Nichols || Goulding)
2017-04-16 13:23:03 -04:00
committed by GitHub

View File

@@ -3,7 +3,7 @@
In our I/O project implementing `grep` in the last chapter, there are some
places where the code could be made clearer and more concise using iterators.
Let's take a look at how iterators can improve our implementation of the
`Config::new` function and the `grep` function.
`Config::new` function and the `search` function.
### Removing a `clone` by Using an Iterator
@@ -19,11 +19,11 @@ impl Config {
return Err("not enough arguments");
}
let search = args[1].clone();
let query = args[1].clone();
let filename = args[2].clone();
Ok(Config {
search: search,
query: query,
filename: filename,
})
}
@@ -35,7 +35,7 @@ could remove them in the future. Well, that time is now! So, why do we need
`clone` here? The issue is that we have a slice with `String` elements in the
parameter `args`, and the `new` function does not own `args`. In order to be
able to return ownership of a `Config` instance, we need to clone the values
that we put in the `search` and `filename` fields of `Config`, so that the
that we put in the `query` and `filename` fields of `Config`, so that the
`Config` instance can own its values.
Now that we know more about iterators, we can change the `new` function to
@@ -78,7 +78,7 @@ know we can call the `next` method on it! Here's the new code:
```rust
# struct Config {
# search: String,
# query: String,
# filename: String,
# }
#
@@ -86,9 +86,9 @@ impl Config {
fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
args.next();
let search = match args.next() {
let query = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a search string"),
None => return Err("Didn't get a query string"),
};
let filename = match args.next() {
@@ -97,7 +97,7 @@ impl Config {
};
Ok(Config {
search: search,
query: query,
filename: filename,
})
}
@@ -109,13 +109,13 @@ impl Config {
Remember that the first value in the return value of `env::args` is the name of
the program. We want to ignore that, so first we'll call `next` and not do
anything with the return value. The second time we call `next` should be the
value we want to put in the `search` field of `Config`. We use a `match` to
value we want to put in the `query` field of `Config`. We use a `match` to
extract the value if `next` returns a `Some`, and we return early with an `Err`
value if there weren't enough arguments (which would cause this call to `next`
to return `None`).
We do the same thing for the `filename` value. It's slightly unfortunate that
the `match` expressions for `search` and `filename` are so similar. It would be
the `match` expressions for `query` and `filename` are so similar. It would be
nice if we could use `?` on the `Option` returned from `next`, but `?` only
works with `Result` values currently. Even if we could use `?` on `Option` like
we can on `Result`, the value we would get would be borrowed, and we want to
@@ -124,18 +124,18 @@ move the `String` from the iterator into `Config`.
### Making Code Clearer with Iterator Adaptors
The other bit of code where we could take advantage of iterators was in the
`grep` function as implemented in Listing 12-15:
`search` function as implemented in Listing 12-15:
<!-- We hadn't had a listing number for this code sample when we submitted
chapter 12; we'll fix the listing numbers in that chapter after you've
reviewed it. /Carol -->
```rust
fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(search) {
if line.contains(query) {
results.push(line);
}
}
@@ -149,30 +149,30 @@ mutable intermediate `results` vector, by using iterator adaptor methods like
this instead:
```rust
fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines()
.filter(|line| line.contains(search))
.filter(|line| line.contains(query))
.collect()
}
```
Here, we use the `filter` adaptor to only keep the lines that
`line.contains(search)` returns true for. We then collect them up into another
`line.contains(query)` returns true for. We then collect them up into another
vector with `collect`. Much simpler!
We can use the same technique in the `grep_case_insensitive` function that we
We can use the same technique in the `search_case_insensitive` function that we
defined in Listing 12-16 as follows:
<!-- Similarly, the code snippet that will be 12-16 didn't have a listing
number when we sent you chapter 12, we will fix it. /Carol -->
```rust
fn grep_case_insensitive<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
let search = search.to_lowercase();
fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
contents.lines()
.filter(|line| {
line.to_lowercase().contains(&search)
line.to_lowercase().contains(&query)
}).collect()
}
```