mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
almost done with ch13.3
This commit is contained in:
parent
ef22125714
commit
ba2884744b
204
Improving The IO Project.md
Normal file
204
Improving The IO Project.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# Improving the I/O Project
|
||||||
|
Using this new info about iterators we can improve the minigrep project by using iterators to make places in the code clearer and more concise
|
||||||
|
|
||||||
|
Lets see how iterators can improve our implementation of the `Config::build` function and the `search` function
|
||||||
|
|
||||||
|
## Removing a `clone` Using an Iterator
|
||||||
|
In before, we added code that took a slice of `String` values and created an instance of the `Config` struct by indexing into the slice and cloning the values, allowing the `Config` struct to own those values.
|
||||||
|
|
||||||
|
Here we have reporduced the implementation fo te `Config::build` function as it was at the end of ch12
|
||||||
|
```rust
|
||||||
|
impl Config {
|
||||||
|
pub fn build(args: &[String]) -> Result<Config, &'static str> {
|
||||||
|
if args.len() < 3 {
|
||||||
|
return Err("not enough arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = args[1].clone();
|
||||||
|
let file_path = args[2].clone();
|
||||||
|
|
||||||
|
let ignore_case = env::var("IGNORE_CASE").is_ok();
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
query,
|
||||||
|
file_path,
|
||||||
|
ignore_case,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Before we said to not worry about the inefficient `clone` calls because we would remove them later.
|
||||||
|
|
||||||
|
Now we will fix that
|
||||||
|
|
||||||
|
We needed `clone` here because we have a slice with `String` elements in the parameter `args`, but the `build` function doesn't own `args`
|
||||||
|
|
||||||
|
To return ownership of a `Config` instance we has to clone the values from the `query` and `file_path` fields of `Config` so the `Config` instance can own its values.
|
||||||
|
|
||||||
|
Now with iterators we can chang the `build` function to take ownership of an iterator as its argument instad of borrowing a slice.
|
||||||
|
|
||||||
|
We will use the iterator functionality instead of the code that checks the length of the slice and indexes into specific locations.
|
||||||
|
|
||||||
|
This will clarify wat the `Config::build` function is doing because the iterator will access the values.
|
||||||
|
|
||||||
|
Once `Config::build` takes ownership of the iterator and stops using indexing operations that borrow.
|
||||||
|
|
||||||
|
We can then move the `String` values from the iterator into `Config` rather than calling `clone` and making a new allocation
|
||||||
|
|
||||||
|
### Using the Returned Iterator Directly
|
||||||
|
The main.rs should look like this
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
|
let config = Config::build(&args).unwrap_or_else(|err| {
|
||||||
|
eprintln!("Problem parsing arguments: {err}");
|
||||||
|
process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --snip--
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
First we will change the start of `main` to use an iterator instead.
|
||||||
|
|
||||||
|
This won't compile until we update `Config::build` as well
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let config = Config::build(env::args()).unwrap_or_else(|err| {
|
||||||
|
eprintln!("Problem parsing arguments: {err}");
|
||||||
|
process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --snip--
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The `env::args` fnction reutrns an iterator.
|
||||||
|
|
||||||
|
Rather than collecting the iterator values into a vector then passing that and then passing a slice to `Config::build`
|
||||||
|
|
||||||
|
Instead we pass ownership of the iterator returned from `env::args` to `Config::build` directly.
|
||||||
|
|
||||||
|
Now we need to update the definition of `Config::build`.
|
||||||
|
|
||||||
|
Here is how we update the signature of `Config::build`.
|
||||||
|
|
||||||
|
Note this still wont compile because we need to update the function body.
|
||||||
|
```rust
|
||||||
|
impl Config {
|
||||||
|
pub fn build(
|
||||||
|
mut args: impl Iterator<Item = String>,
|
||||||
|
) -> Result<Config, &'static str> {
|
||||||
|
// --snip--
|
||||||
|
```
|
||||||
|
The std library documentation for the `env::args` function sohws that the type of the iterator it returns is `std::env::Args` and that type implements the `Iterator` trait and returns `String` values.
|
||||||
|
|
||||||
|
Now we updated the `Config::build` signature so the paramter `args` has a generic type wioth the trait bounds `impl Iterator<Item = String>` instead of `&[String]`
|
||||||
|
|
||||||
|
This useage of the `impl Trait` syntax was discuess in the [Traits and Paramters](./Traits.md#traits-as-parameters).
|
||||||
|
|
||||||
|
This means that `args` can be any type that implements the `Iterator` trait and returns `String`
|
||||||
|
|
||||||
|
Because we take ownership of `args`, then we well will be mutating `args` by iterating over it.
|
||||||
|
|
||||||
|
We add the `mut` keyword into the sepcification of the `argsg` paramter to ensure it is mutable
|
||||||
|
|
||||||
|
### Using `Iterator` Trait Methods Instead of Indexing
|
||||||
|
Now we will fix the body of `Config::build`
|
||||||
|
|
||||||
|
Due to how `args` implements the `Iterator` trait, we know we can call the `next` method on it
|
||||||
|
|
||||||
|
Here is the update body
|
||||||
|
```rust
|
||||||
|
impl Config {
|
||||||
|
pub fn build(
|
||||||
|
mut args: impl Iterator<Item = String>,
|
||||||
|
) -> Result<Config, &'static str> {
|
||||||
|
args.next();
|
||||||
|
|
||||||
|
let query = match args.next() {
|
||||||
|
Some(arg) => arg,
|
||||||
|
None => return Err("Didn't get a query string"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_path = match args.next() {
|
||||||
|
Some(arg) => arg,
|
||||||
|
None => return Err("Didn't get a file path"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ignore_case = env::var("IGNORE_CASE").is_ok();
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
query,
|
||||||
|
file_path,
|
||||||
|
ignore_case,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember that the first value in the return value of `env::args` is the name of the program
|
||||||
|
|
||||||
|
We want to ignore that, we first call `next` and do nothing with the return value to consume it from the iterator.
|
||||||
|
|
||||||
|
Next we call `net` to get the value we want to put in the `query field` of `Config`
|
||||||
|
|
||||||
|
If `next` returns a `Some`, we use a `match` to extract the value.
|
||||||
|
|
||||||
|
If it returns `None`, it means not enough arguments were given and we return early with an `Err` value.
|
||||||
|
|
||||||
|
We do the same thing for the `file_path` value.
|
||||||
|
|
||||||
|
## Making Code Clearer with Iterator Adapters
|
||||||
|
|
||||||
|
We can also take advantage of iterators in the `search` function in the I/O project.
|
||||||
|
|
||||||
|
Here is the old version of the `search` function
|
||||||
|
```rust
|
||||||
|
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
for line in contents.lines() {
|
||||||
|
if line.contains(query) {
|
||||||
|
results.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
```
|
||||||
|
To rewrite this code in a more concise way by using adapter methods.
|
||||||
|
|
||||||
|
This helps us avoid having a mutable intermediate `results` vector.
|
||||||
|
|
||||||
|
The functional programming style prefers to minimize the amount of mutable state to make code clearer.
|
||||||
|
|
||||||
|
Removing the mutable state might enable a future enhancement to make searching happen in parallel, because we wouldn't have to manage concurrent access to the `results` vector
|
||||||
|
|
||||||
|
Here is the new change
|
||||||
|
```rust
|
||||||
|
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||||
|
contents
|
||||||
|
.lines()
|
||||||
|
.filter(|line| line.contains(query))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The purpose of the `search` function is to return all lines in `contents` that contain the `query`.
|
||||||
|
|
||||||
|
Similar to the filter example form before, this code uses the `filter` adapter to keep only the lines that `line.contains(query)` returns `true`.
|
||||||
|
|
||||||
|
We collect the matching lines into another vector with `collect`.
|
||||||
|
|
||||||
|
This is much simpler.
|
||||||
|
|
||||||
|
You can also make the change to use iterator methods in `search_case_insensitive` function as well.
|
||||||
|
|
||||||
|
## Choosing Between Loops or Iterators
|
||||||
|
The next question is which sytle you should choose in your own code and why
|
||||||
|
|
||||||
|
The original implementation of minigrep verses using iterators.
|
||||||
|
|
||||||
|
Most Rust programmers prefer to use the iterator style.
|
||||||
|
|
||||||
|
It is a bit tougher to get the hand of at first, once you get the feel for the various
|
@ -12,7 +12,7 @@ It will discuss some features of Rust that are similar to features in many langu
|
|||||||
It will cover:
|
It will cover:
|
||||||
- [*Closures*](./Closures.md) - a function-like construct you can store in a variable
|
- [*Closures*](./Closures.md) - a function-like construct you can store in a variable
|
||||||
- [*Iterators*](./Iterators.md) - a way of processing a series of elements
|
- [*Iterators*](./Iterators.md) - a way of processing a series of elements
|
||||||
- How to use colsure and iterators to improve the I/O project (minigrep)
|
- How to use colsure and iterators to [improve the I/O project (minigrep)](./Improving%20The%20IO%20Project.md)
|
||||||
- The preformance of closures and iterators (Spoiler alert: they are faster than you might think!)
|
- The preformance of closures and iterators (Spoiler alert: they are faster than you might think!)
|
||||||
|
|
||||||
We have already covered some other Rust freatures, such as pattern matchin and enums, that are also influenced by the functional style.
|
We have already covered some other Rust freatures, such as pattern matchin and enums, that are also influenced by the functional style.
|
||||||
|
@ -10,17 +10,43 @@ pub struct Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn build(args: &[String]) -> Result<Config, &'static str> {
|
// refactor 14
|
||||||
if args.len() < 3 {
|
// pub fn build(args: &[String]) -> Result<Config, &'static str> {
|
||||||
return Err("not enough arguments");
|
// if args.len() < 3 {
|
||||||
}
|
// return Err("not enough arguments");
|
||||||
|
// }
|
||||||
|
|
||||||
let query = args[1].clone();
|
// let query = args[1].clone();
|
||||||
let file_path = args[2].clone();
|
// let file_path = args[2].clone();
|
||||||
|
|
||||||
|
// let ignore_case = env::var("IGNORE_CASE").is_ok();
|
||||||
|
|
||||||
|
// Ok(Config { query, file_path, ignore_case })
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn build(mut args: impl Iterator<Item = String>,) -> Result<Config, &'static str> {
|
||||||
|
// --snip-- for now
|
||||||
|
|
||||||
|
// skip first value in iterator, value inside is name of program
|
||||||
|
args.next();
|
||||||
|
|
||||||
|
let query = match args.next() {
|
||||||
|
Some(arg) => arg,
|
||||||
|
None => return Err("Didn't get a query string"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_path = match args.next() {
|
||||||
|
Some(arg) => arg,
|
||||||
|
None => return Err("Didn't get a file path")
|
||||||
|
};
|
||||||
|
|
||||||
let ignore_case = env::var("IGNORE_CASE").is_ok();
|
let ignore_case = env::var("IGNORE_CASE").is_ok();
|
||||||
|
|
||||||
Ok(Config { query, file_path, ignore_case })
|
Ok(Config {
|
||||||
|
query,
|
||||||
|
file_path,
|
||||||
|
ignore_case,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,17 +75,23 @@ pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
|
||||||
// original that only can fail
|
// refacotr 15
|
||||||
// vec![]
|
// // original that only can fail
|
||||||
let mut results = Vec::new();
|
// // vec![]
|
||||||
|
// let mut results = Vec::new();
|
||||||
|
|
||||||
for line in contents.lines() {
|
// for line in contents.lines() {
|
||||||
if line.contains(query) {
|
// if line.contains(query) {
|
||||||
// do something with line
|
// // do something with line
|
||||||
results.push(line);
|
// results.push(line);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
results
|
// results
|
||||||
|
|
||||||
|
contents
|
||||||
|
.lines()
|
||||||
|
.filter(|line| line.contains(query))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_case_insensitive<'a>(
|
pub fn search_case_insensitive<'a>(
|
||||||
|
@ -6,7 +6,8 @@ use std::process;
|
|||||||
use minigrep::Config;
|
use minigrep::Config;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args: Vec<String> = env::args().collect();
|
// refactor 13
|
||||||
|
// let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
// dbg!(args);
|
// dbg!(args);
|
||||||
|
|
||||||
@ -22,11 +23,17 @@ fn main() {
|
|||||||
// refactor 3
|
// refactor 3
|
||||||
// let config = Config::new(&args);
|
// let config = Config::new(&args);
|
||||||
|
|
||||||
// recfactor 6
|
// // recfactor 6
|
||||||
let config = Config::build(&args).unwrap_or_else(|err| {
|
// let config = Config::build(&args).unwrap_or_else(|err| {
|
||||||
// refactor 14
|
// // refactor 14
|
||||||
// println!("Problem parsing arguments: {err}");
|
// // println!("Problem parsing arguments: {err}");
|
||||||
// process::exit(1);
|
// // process::exit(1);
|
||||||
|
// eprintln!("Problem parsing arguments: {err}");
|
||||||
|
// process::exit(1);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// refactor 13
|
||||||
|
let config = Config::build(env::args()).unwrap_or_else(|err| {
|
||||||
eprintln!("Problem parsing arguments: {err}");
|
eprintln!("Problem parsing arguments: {err}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user