Compare commits

...

4 Commits

Author SHA1 Message Date
c47bd9cfae finished ch13.4 and finished ch13
All checks were successful
Test Gitea Actions / first (push) Successful in 13s
2025-02-24 17:06:04 -07:00
d961dd4ee9 finished 13.3 2025-02-24 16:02:55 -07:00
ba2884744b almost done with ch13.3 2025-02-24 15:35:01 -07:00
ef22125714 finished ch13.2 2025-02-24 13:11:28 -07:00
9 changed files with 657 additions and 29 deletions

212
Improving The IO Project.md Normal file
View File

@ -0,0 +1,212 @@
# 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 iterator adaptor and what they do.
Iterators can be easier to understand.
Instead of fiddling with various bits of looping and building new vectors, the code focuses on high-level objective of the loop.
This abraction takes away some of the commonplace code so it is easier to see the concepts that are unique to this code, such as the filtering condition each element in the iteraor must pass.
You may think that the low level low will be but lets talk about performance [here](./The%20preformance%20Closures%20and%20Iterators.md).

View File

@ -11,10 +11,21 @@ It will discuss some features of Rust that are similar to features in many langu
It will cover:
- [*Closures*](./Closures.md) - a function-like construct you can store in a variable
- *Iterators* - a way of processing a series of elements
- How to use colsure and iterators to improve the I/O project (minigrep)
- The preformance of closures and iterators (Spoiler alert: they are faster than you might think!)
- [*Iterators*](./Iterators.md) - a way of processing a series of elements
- How to use colsure and iterators to [improve the I/O project (minigrep)](./Improving%20The%20IO%20Project.md)
- [The preformance of closures and iterators](./The%20preformance%20Closures%20and%20Iterators.md) (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.
Mastering closures and iterators is an improtant part of wiritng idiomatic, fast Rust code.
Mastering closures and iterators is an improtant part of wiritng idiomatic, fast Rust code.
## Summary
Closures and iterators are Rust features inspired by functional programming language ideas.
They contribute to the capability to express high-level ideas at low-level preformance.
The implementations of closures and iterators are such that runtime performance is not affected.
This is part of Rust's goal to strive to provide zero-cost abstractions.

275
Iterators.md Normal file
View File

@ -0,0 +1,275 @@
# Processing a Series of Items with Iterators
Iterator patterns allow you to perform some task on a sequence of items in turn.
An iterator is responsibl for the logic of iterating over each item and determining when the sequence has finished.
When you use iterators, you don't have to reimplement that logic yourself again.
Rust's iterators are *lasy*, meaning they have no effect until yo call methods that consume the iterator to use it up
In this example the code creates an iterator over the items in the vecotr `v1` by calling the `iter` method defined on `Vec<T>`
This code does nothing useful.
```rust
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
```
The iterator is stored in the `v1_iter` variable.
Once an iterator is created, we can use it in a variety of ways.
Before we iterator over an array using a `for` loop to execute some code on each of its items.
Under the hood this implicitly created and then consumed an iterator, but we glossed over how exactly that works until now.
In this example, we separate the creation of the iterator from the use of the iterator in the `for` loop.
When the `for` loop is called using th iterator in `v1_iter`, each element in the iterator is used in one iteration of the loop which prints out each value.
```rust
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {val}");
}
```
In languages that don't have iterators provided by their std libraries, you would likely wirte this same functionality by starting at index 0.
Using that variable to index into th vector to get a value and incrememting the variable in a loop until it reached the total number of items in the vector.
Iterators handle all that logic for you, removing repetitive code that you could mess up.
Iterators gives mroe flexibility to use the same logic with many different kinds of sequences, not just data structs you can index into (vectors for example).
## The `Iterator` Trait and the `next` Method
All iterators implement a trait named `Iterator` that is defined in the std library
The defintion of the `Iterator` trait looks like this:
```rust
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// methods with default implementations elided
}
```
Note this definition uses some new syntax, `type Item` and `Self::Item`, which are defining an *associated type* with this trait
Associated types will be discussed in ch20 (Advanced Features)
For now know that this code says implementing the `Iterator` trait requires you also defined an `Item` type.
This `Item` type is usd in the return in the return type of the `next` method.
The `Item` tpye ill be the type returned form the iterator.
The `Iterator` trait only requires implmentors to define one method; `next` method which returns one item of the iterator at a time wrapped in `Some` and when the iteration is over, returns `None`.
We can call the `next` method on iterators directly
This example demonstrates what values are returned from repeated calls to `next` on the iterator created from the vector.
```rust
#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
}
```
Notice that we need to make `v1_iter` mutable.
Calling the `next` method on an iterator changes internal state that the iterator uses to keep track of where it is in the sequence.
It could also be said that the code *consumes*, or uses up, the iterator.
Each call to `next` comsumes an item form the iterator.
We didn't need to make `v1_iter` mutable when we used a `for` loop because the loop took ownership of `v1_iter` and made it mutable under the hood.
Note as well the values that we get from the `next` are immutable reference to the values in the vector.
The `iter` mthod produces an iterator over immutable references.
If we want to create an iterator that takes ownership of `v1` and returns owned values, we can call `into_iter`.
If you want to iterate over mutalbe references, you can call `iter_mut`
## Methods that Consume the Iterator
The `Iterator` trait has a number of different methods with default implementations provided by the std library.
You can find out about these by looking the std library API documentation for the `Iterator` trait.
Some these methods call the `next` method in their definition, which is why you are required to implement the `Iterator` trait.
Methods that call `next` are called *consuming adapters*, becaise calling them uses up the iterator.
One example is the `sum` metod which takes ownership of the iterator and iterates through the items by repeatedly calling `next` thus consuming the iterator.
As it iterates through it adds each item to a running total, this comsumes the iterator.
When it is complete it return the total
Here is a test illustrating a use of the `sum` method
```rust
#[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
}
```
We aren't allowed to use `v1_iter` after the call to `sum because it takes ownership of the iterator we call it on.
## Methods that Produce Other Iterators
*Iterator adapters* are methods defined on the `Iterator` trait that don't consume the iterator.
Instead, they produce different iterators by changing some aspect of the original iterator.
This example shows calling an iterator adapter method `map`, which takes a closure to call on each item as the items are iterated through.
The `map` method returns a new iterator that produces the modified items.
The closure here creates a new iterator in which each item from the vector will be incremeted by 1
```rust
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
```
But this code produces a warning
```
$ cargo run
Compiling iterators v0.1.0 (file:///projects/iterators)
warning: unused `Map` that must be used
--> src/main.rs:4:5
|
4 | v1.iter().map(|x| x + 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: iterators are lazy and do nothing unless consumed
= note: `#[warn(unused_must_use)]` on by default
help: use `let _ = ...` to ignore the resulting value
|
4 | let _ = v1.iter().map(|x| x + 1);
| +++++++
warning: `iterators` (bin "iterators") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/iterators`
```
This doesnt do anything. The closure we sepcified never gets called. The warning reminds us why: iterator adapters are lazy and we need to consume the iterator here.
To fix this warning and consume the iterator, we will use the `collect` method, which we used in Ch 12 with `env::args`.
This method consumes the iterator and collects the resulting values into a collection data type.
In this next example we collect the results of the iterating over the iterator that is returned form the call to `map` into a vector.
This vector will end up containing each item form the original vector incremeted by 1.
```rust
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
```
Because `map` takes a closure, we can specify any operation we want to perform on each item.
This is a good eample of how closures let you do custome behavior while reusing the iteration behavior that the `Iterator` trait provides.
You can chain mutliple calls to iterator adapters to perform compex action is a readable way.
Due to all iterators being lasy, you have to call one of the consuming adapter methods to get results ffrom calls to iterator adapters.
## Using Closures that Capture Their Environment
Many iterator adapters take closures as args and commonly the closures we will specify as ags to iterator adapters will be closures that capture their environment.
In this example we use the `filter` method that takes a closure.
The closure get an item form the iterator and returns a `bool`
If the closure returns `true`, the value will be included in the iteration produced by `filter`.
If the closure returns `false`, the value won't be included.
Here we use `filer` with a closure that captures the `shoe_size` variable from its environment to iterate over a collection of `Shoe` struct instances.
It will return only shoes that are the specified size.
```rust
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn filters_by_size() {
let shoes = vec![
Shoe {
size: 10,
style: String::from("sneaker"),
},
Shoe {
size: 13,
style: String::from("sandal"),
},
Shoe {
size: 10,
style: String::from("boot"),
},
];
let in_my_size = shoes_in_size(shoes, 10);
assert_eq!(
in_my_size,
vec![
Shoe {
size: 10,
style: String::from("sneaker")
},
Shoe {
size: 10,
style: String::from("boot")
},
]
);
}
}
```
The `shoes_in_size` function takes ownership of a vector of shoes and a shoe size parameters.
It returns a vector containing only shoes of the specified size.
In the body of `shoes_in_size` we call `into_iter` to create an iterator that takes ownership of the vector.
We then call `filter` to adapt that iterator into a new iterator that only contains elements for which the closure returns `true`.
The closure captures the `shoe_size` parameter from the environment and compares the value with each shoe's size, keeping only shoes of the size specified.
Calling `collect` finally gathers the values returned by the adapted iterator into a vector that is returned by the function.
This test shows that when we call `shoes_in_size`, we get back only shoes that have the same size as te value specified.

View File

@ -0,0 +1,84 @@
# Comparing Performance: Loops vs. Iterators
To determince whether to use loops or iterators, you need to know which implementation is faster
Is it the version with an explicit `for` loop?
Is it the version with iterators?
Here is a benchmark where we load the entire contnets of *The Adventures of Sherlock Holmes* by Sir Arthur Conan Doyle into a `String` and looking ofr the word *the* in the contents
Here is the results of the benchmark on the version of `search` using the `for` loop and the version using iterators:
```
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
```
The two have similar performace!
We won't explain the benchmark code here, because the poin is not to prive that the two versions are equivalent but to get a general sense of how these two compare performance-wise.
For a more comprehensive benchmark, you should check using various texts of various sizes as the `contents`, different words and words of different lenghts as the `query` and all kinds of other variations.
The point is this, iterators, although a high-level abstraction, get compiled down to roughly the same code as if you wrote the lower-level code yourself.
Iterators are one of Rust's *zero-cost abstractions`. This means using the abstraction imposes no addtional runtime overhead.
This is analogous to how Bjarne Stroustrup, the original designer and implementor of C++ defines *zero-verhead* in "Fondations of C++" (2012)
```
In general, C++ implementations obey the zero-overhead principle: What you dont use, you dont pay for. And further: What you do use, you couldnt hand code any better.
```
Here is another example, the code is taken from an audio decoder.
The decoding algorithm uses the linear prediction mathematical operation to estimate future values based on a linear function of the previous samples.
This code uses an iterator chain to do some math on three variables in scope:
- a `buffer` slice of data
- an array of `coefficients`
- an amount ot which shift data in `qlp_shift`
We declared the variables within this example but not given them any values.
Even though this code doesnt have muc meaning outside of its context, it is still a concise, real-world example of how Rust tranlate high-level ideas to low-level code.
```rust
let buffer: &mut [i32];
let coefficients: [i64; 12];
let qlp_shift: i16;
for i in 12..buffer.len() {
let prediction = coefficients.iter()
.zip(&buffer[i - 12..i])
.map(|(&c, &s)| c * s as i64)
.sum::<i64>() >> qlp_shift;
let delta = buffer[i];
buffer[i] = prediction as i32 + delta;
}
```
To calcuate the value of `prediction`, the code iterates though each of he 12 values in `coefficients` and ses the `zip` method to pair the coefficient values with the previous 12 values in `buffer`
For each pair then we multiply the values together, sum all the results and shift the bits in the sum `qlp_shift` bits to the right.
Calculations in applications like audio decoders often prioritize perfomance.
Here we are creating an iterator, using two adapters and then consuming the value.
What assembly would this Rust code compile into.
As of writing this in the book, it compiles down to the same assbly you would write by hand.
There is no loop at all corresponding to the iteration over the values in `coefficients`.
Rust knows that there are 12 iterations, so it "unrolls" the loop.
*Unrolling* is an optimization that removes the overhead of the loop controlling code and instead generates repetitive code for each iteration fo the loop.
All of the coefficients ge stored in registers which means accessing the values is very fast.
There are no bounds checks on the array access at runtime.
All of these optimizations that Rust is able to apply make the resulting code extremely efficient.
Now you know that you can use iterators and closures without fear of performance hits.
They make code seem like its highe level but again dont impose a performance hit.

7
adder/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adder"
version = "0.1.0"

View File

@ -1 +1 @@
{"rustc_fingerprint":13662911779824945272,"outputs":{"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.83.0 (90b35a623 2024-11-26)\nbinary: rustc\ncommit-hash: 90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf\ncommit-date: 2024-11-26\nhost: x86_64-pc-windows-msvc\nrelease: 1.83.0\nLLVM version: 19.1.1\n","stderr":""},"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Brock\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nfmt_debug=\"full\"\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"lahfsahf\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"128\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"128\"\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"pc\"\nub_checks\nwindows\n","stderr":""},"12744816824612481171":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Brock\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":4305361489769004817,"outputs":{"13331785392996375709":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brock/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n","stderr":""}},"successes":{}}

View File

@ -1 +1 @@
{"rustc_fingerprint":13662911779824945272,"outputs":{"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Brock\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nfmt_debug=\"full\"\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"lahfsahf\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"128\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"128\"\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"pc\"\nub_checks\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.83.0 (90b35a623 2024-11-26)\nbinary: rustc\ncommit-hash: 90b35a6239c3d8bdabc530a6a0816f7ff89a0aaf\ncommit-date: 2024-11-26\nhost: x86_64-pc-windows-msvc\nrelease: 1.83.0\nLLVM version: 19.1.1\n","stderr":""},"12744816824612481171":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Brock\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}
{"rustc_fingerprint":4305361489769004817,"outputs":{"13331785392996375709":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/brock/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.85.0 (4d91de4e4 2025-02-17)\nbinary: rustc\ncommit-hash: 4d91de4e48198da2e33413efdcd9cd2cc0c46688\ncommit-date: 2025-02-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.85.0\nLLVM version: 19.1.7\n","stderr":""}},"successes":{}}

View File

@ -10,17 +10,43 @@ pub struct Config {
}
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
// refactor 14
// 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 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 })
// }
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();
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> {
// original that only can fail
// vec![]
let mut results = Vec::new();
// refacotr 15
// // original that only can fail
// // vec![]
// let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
// do something with line
results.push(line);
}
}
results
// for line in contents.lines() {
// if line.contains(query) {
// // do something with line
// results.push(line);
// }
// }
// results
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}
pub fn search_case_insensitive<'a>(

View File

@ -6,7 +6,8 @@ use std::process;
use minigrep::Config;
fn main() {
let args: Vec<String> = env::args().collect();
// refactor 13
// let args: Vec<String> = env::args().collect();
// dbg!(args);
@ -22,11 +23,17 @@ fn main() {
// refactor 3
// let config = Config::new(&args);
// recfactor 6
let config = Config::build(&args).unwrap_or_else(|err| {
// refactor 14
// println!("Problem parsing arguments: {err}");
// process::exit(1);
// // recfactor 6
// let config = Config::build(&args).unwrap_or_else(|err| {
// // refactor 14
// // println!("Problem parsing arguments: {err}");
// // 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}");
process::exit(1);
});