You've already forked comprehensive-rust
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:
@ -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
42
src/iterators/helpers.md
Normal 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>
|
@ -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:
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
59
src/iterators/motivation.md
Normal file
59
src/iterators/motivation.md
Normal 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>
|
Reference in New Issue
Block a user