1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-07-02 05:04:29 +02:00

Rework iterator section (#2523)

This commit is contained in:
Nicole L
2024-12-17 15:59:39 -08:00
committed by GitHub
parent e902b1ef60
commit 7f0c591b0b
5 changed files with 152 additions and 26 deletions

View File

@ -171,9 +171,11 @@
- [Welcome](welcome-day-4.md) - [Welcome](welcome-day-4.md)
- [Iterators](iterators.md) - [Iterators](iterators.md)
- [`Iterator`](iterators/iterator.md) - [Motivation](iterators/motivation.md)
- [`IntoIterator`](iterators/intoiterator.md) - [`Iterator` Trait](iterators/iterator.md)
- [`Iterator` Helper Methods](iterators/helpers.md)
- [`collect`](iterators/collect.md) - [`collect`](iterators/collect.md)
- [`IntoIterator`](iterators/intoiterator.md)
- [Exercise: Iterator Method Chaining](iterators/exercise.md) - [Exercise: Iterator Method Chaining](iterators/exercise.md)
- [Solution](iterators/solution.md) - [Solution](iterators/solution.md)
- [Modules](modules.md) - [Modules](modules.md)

42
src/iterators/helpers.md Normal file
View File

@ -0,0 +1,42 @@
---
minutes: 5
---
# `Iterator` Helper Methods
In addition to the `next` method that defines how an iterator behaves, the
`Iterator` trait provides 70+ helper methods that can be used to build
customized iterators.
```rust,editable
let result: i32 = (1..=10) // Create a range from 1 to 10
.filter(|&x| x % 2 == 0) // Keep only even numbers
.map(|x| x * x) // Square each number
.sum(); // Sum up all the squared numbers
println!("The sum of squares of even numbers from 1 to 10 is: {}", result);
```
<details>
- The `Iterator` trait implements many common functional programming operations
over collections (e.g. `map`, `filter`, `reduce`, etc). This is the trait
where you can find all the documentation about them.
- Many of these helper methods take the original iterator and produce a new
iterator with different behavior. These are know as "iterator adapter
methods".
- Some methods, like `sum` and `count`, consume the iterator and pull all of the
elements out of it.
- These methods are designed to be chained together so that it's easy to build a
custom iterator that does exactly what you need.
## More to Explore
- Rust's iterators are extremely efficient and highly optimizable. Even complex
iterators made by combining many adapter methods will still result in code as
efficient as equivalent imperative implementations.
</details>

View File

@ -57,6 +57,11 @@ fn main() {
<details> <details>
- `IntoIterator` is the trait that makes for loops work. It is implemented by
collection types such as `Vec<T>` and references to them such as `&Vec<T>` and
`&[T]`. Ranges also implement it. This is why you can iterate over a vector
with `for i in some_vec { .. }` but `some_vec.next()` doesn't exist.
Click through to the docs for `IntoIterator`. Every implementation of Click through to the docs for `IntoIterator`. Every implementation of
`IntoIterator` must declare two types: `IntoIterator` must declare two types:

View File

@ -2,49 +2,67 @@
minutes: 5 minutes: 5
--- ---
# `Iterator` # `Iterator` Trait
The [`Iterator`][1] trait supports iterating over values in a collection. It The [`Iterator`][1] trait defines how an object can be used to produce a
requires a `next` method and provides lots of methods. Many standard library sequence of values. For example, if we wanted to create an iterator that can
types implement `Iterator`, and you can implement it yourself, too: produce the elements of a slice it might look something like this:
```rust,editable ```rust,editable
struct Fibonacci { struct SliceIter<'s> {
curr: u32, slice: &'s [i32],
next: u32, i: usize,
} }
impl Iterator for Fibonacci { impl<'s> Iterator for SliceIter<'s> {
type Item = u32; type Item = &'s i32;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let new_next = self.curr + self.next; if self.i == self.slice.len() {
self.curr = self.next; None
self.next = new_next; } else {
Some(self.curr) let next = &self.slice[self.i];
self.i += 1;
Some(next)
}
} }
} }
fn main() { fn main() {
let fib = Fibonacci { curr: 0, next: 1 }; let slice = [2, 4, 6, 8].as_slice();
for (i, n) in fib.enumerate().take(5) { let iter = SliceIter { slice, i: 0 };
println!("fib({i}): {n}"); for elem in iter {
println!("elem: {elem}");
} }
} }
``` ```
<details> <details>
- The `Iterator` trait implements many common functional programming operations - The `SliceIter` example implements the same logic as the C-style `for` loop
over collections (e.g. `map`, `filter`, `reduce`, etc). This is the trait demonstrated on the last slide.
where you can find all the documentation about them. In Rust these functions
should produce the code as efficient as equivalent imperative implementations.
- `IntoIterator` is the trait that makes for loops work. It is implemented by - Point out to the students that iterators are lazy: Creating the iterator just
collection types such as `Vec<T>` and references to them such as `&Vec<T>` and initializes the struct but does not otherwise do any work. No work happens
`&[T]`. Ranges also implement it. This is why you can iterate over a vector until the `next` method is called.
with `for i in some_vec { .. }` but `some_vec.next()` doesn't exist.
- Iterators don't need to be finite! It's entirely valid to have an iterator
that will produce values forever. For example, a half open range like `0..`
will keep going until integer overflow occurs.
## More to Explore
- The "real" version of `SliceIter` is the [`slice::Iter`][2] type in the
standard library, however the real version uses pointers under the hood
instead of an index in order to eliminate bounds checks.
- The `SliceIter` example is a good example of a struct that contains a
reference and therefore uses lifetime annotations.
- You can also demonstrate adding a generic parameter to `SliceIter` to allow it
to work with any kind of slice (not just `&[i32]`).
</details> </details>
[1]: https://doc.rust-lang.org/std/iter/trait.Iterator.html [1]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
[2]: https://doc.rust-lang.org/stable/std/slice/struct.Iter.html

View File

@ -0,0 +1,59 @@
---
minutes: 3
---
# Motivating Iterators
If you want to iterate over the contents of an array, you'll need to define:
- Some state to keep track of where you are in the iteration process, e.g. an
index.
- A condition to determine when iteration is done.
- Logic for updating the state of iteration each loop.
- Logic for fetching each element using that iteration state.
In a C-style for loop you declare these things directly:
```c,editable
for (int i = 0; i < array_len; i += 1) {
int elem = array[i];
}
```
In Rust we bundle this state and logic together into an object known as an
"iterator".
<details>
- This slide provides context for what Rust iterators do under the hood. We use
the (hopefully) familiar construct of a C-style `for` loop to show how
iteration requires some state and some logic, that way on the next slide we
can show how an iterator bundles these together.
- Rust doesn't have a C-style `for` loop, but we can express the same thing with
`while`:
```rust,editable
let array = [2, 4, 6, 8];
let mut i = 0;
while i < array.len() {
let elem = array[i];
i += 1;
}
```
## More to Explore
There's another way to express array iteration using `for` in C and C++: You can
use a pointer to the front and a pointer to the end of the array and then
compare those pointers to determine when the loop should end.
```c,editable
for (int *ptr = array; ptr < array + len; ptr += 1) {
int elem = *ptr;
}
```
If students ask, you can point out that this is how Rust's slice and array
iterators work under the hood (though implemented as a Rust iterator).
</details>