Skip to content

Commit 7f0c591

Browse files
authored
Rework iterator section (#2523)
1 parent e902b1e commit 7f0c591

File tree

5 files changed

+152
-26
lines changed

5 files changed

+152
-26
lines changed

src/SUMMARY.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,11 @@
171171

172172
- [Welcome](welcome-day-4.md)
173173
- [Iterators](iterators.md)
174-
- [`Iterator`](iterators/iterator.md)
175-
- [`IntoIterator`](iterators/intoiterator.md)
174+
- [Motivation](iterators/motivation.md)
175+
- [`Iterator` Trait](iterators/iterator.md)
176+
- [`Iterator` Helper Methods](iterators/helpers.md)
176177
- [`collect`](iterators/collect.md)
178+
- [`IntoIterator`](iterators/intoiterator.md)
177179
- [Exercise: Iterator Method Chaining](iterators/exercise.md)
178180
- [Solution](iterators/solution.md)
179181
- [Modules](modules.md)

src/iterators/helpers.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
minutes: 5
3+
---
4+
5+
# `Iterator` Helper Methods
6+
7+
In addition to the `next` method that defines how an iterator behaves, the
8+
`Iterator` trait provides 70+ helper methods that can be used to build
9+
customized iterators.
10+
11+
```rust,editable
12+
let result: i32 = (1..=10) // Create a range from 1 to 10
13+
.filter(|&x| x % 2 == 0) // Keep only even numbers
14+
.map(|x| x * x) // Square each number
15+
.sum(); // Sum up all the squared numbers
16+
17+
println!("The sum of squares of even numbers from 1 to 10 is: {}", result);
18+
```
19+
20+
<details>
21+
22+
- The `Iterator` trait implements many common functional programming operations
23+
over collections (e.g. `map`, `filter`, `reduce`, etc). This is the trait
24+
where you can find all the documentation about them.
25+
26+
- Many of these helper methods take the original iterator and produce a new
27+
iterator with different behavior. These are know as "iterator adapter
28+
methods".
29+
30+
- Some methods, like `sum` and `count`, consume the iterator and pull all of the
31+
elements out of it.
32+
33+
- These methods are designed to be chained together so that it's easy to build a
34+
custom iterator that does exactly what you need.
35+
36+
## More to Explore
37+
38+
- Rust's iterators are extremely efficient and highly optimizable. Even complex
39+
iterators made by combining many adapter methods will still result in code as
40+
efficient as equivalent imperative implementations.
41+
42+
</details>

src/iterators/intoiterator.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ fn main() {
5757

5858
<details>
5959

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

src/iterators/iterator.md

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,67 @@
22
minutes: 5
33
---
44

5-
# `Iterator`
5+
# `Iterator` Trait
66

7-
The [`Iterator`][1] trait supports iterating over values in a collection. It
8-
requires a `next` method and provides lots of methods. Many standard library
9-
types implement `Iterator`, and you can implement it yourself, too:
7+
The [`Iterator`][1] trait defines how an object can be used to produce a
8+
sequence of values. For example, if we wanted to create an iterator that can
9+
produce the elements of a slice it might look something like this:
1010

1111
```rust,editable
12-
struct Fibonacci {
13-
curr: u32,
14-
next: u32,
12+
struct SliceIter<'s> {
13+
slice: &'s [i32],
14+
i: usize,
1515
}
1616
17-
impl Iterator for Fibonacci {
18-
type Item = u32;
17+
impl<'s> Iterator for SliceIter<'s> {
18+
type Item = &'s i32;
1919
2020
fn next(&mut self) -> Option<Self::Item> {
21-
let new_next = self.curr + self.next;
22-
self.curr = self.next;
23-
self.next = new_next;
24-
Some(self.curr)
21+
if self.i == self.slice.len() {
22+
None
23+
} else {
24+
let next = &self.slice[self.i];
25+
self.i += 1;
26+
Some(next)
27+
}
2528
}
2629
}
2730
2831
fn main() {
29-
let fib = Fibonacci { curr: 0, next: 1 };
30-
for (i, n) in fib.enumerate().take(5) {
31-
println!("fib({i}): {n}");
32+
let slice = [2, 4, 6, 8].as_slice();
33+
let iter = SliceIter { slice, i: 0 };
34+
for elem in iter {
35+
println!("elem: {elem}");
3236
}
3337
}
3438
```
3539

3640
<details>
3741

38-
- The `Iterator` trait implements many common functional programming operations
39-
over collections (e.g. `map`, `filter`, `reduce`, etc). This is the trait
40-
where you can find all the documentation about them. In Rust these functions
41-
should produce the code as efficient as equivalent imperative implementations.
42+
- The `SliceIter` example implements the same logic as the C-style `for` loop
43+
demonstrated on the last slide.
4244

43-
- `IntoIterator` is the trait that makes for loops work. It is implemented by
44-
collection types such as `Vec<T>` and references to them such as `&Vec<T>` and
45-
`&[T]`. Ranges also implement it. This is why you can iterate over a vector
46-
with `for i in some_vec { .. }` but `some_vec.next()` doesn't exist.
45+
- Point out to the students that iterators are lazy: Creating the iterator just
46+
initializes the struct but does not otherwise do any work. No work happens
47+
until the `next` method is called.
48+
49+
- Iterators don't need to be finite! It's entirely valid to have an iterator
50+
that will produce values forever. For example, a half open range like `0..`
51+
will keep going until integer overflow occurs.
52+
53+
## More to Explore
54+
55+
- The "real" version of `SliceIter` is the [`slice::Iter`][2] type in the
56+
standard library, however the real version uses pointers under the hood
57+
instead of an index in order to eliminate bounds checks.
58+
59+
- The `SliceIter` example is a good example of a struct that contains a
60+
reference and therefore uses lifetime annotations.
61+
62+
- You can also demonstrate adding a generic parameter to `SliceIter` to allow it
63+
to work with any kind of slice (not just `&[i32]`).
4764

4865
</details>
4966

5067
[1]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
68+
[2]: https://doc.rust-lang.org/stable/std/slice/struct.Iter.html

src/iterators/motivation.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
minutes: 3
3+
---
4+
5+
# Motivating Iterators
6+
7+
If you want to iterate over the contents of an array, you'll need to define:
8+
9+
- Some state to keep track of where you are in the iteration process, e.g. an
10+
index.
11+
- A condition to determine when iteration is done.
12+
- Logic for updating the state of iteration each loop.
13+
- Logic for fetching each element using that iteration state.
14+
15+
In a C-style for loop you declare these things directly:
16+
17+
```c,editable
18+
for (int i = 0; i < array_len; i += 1) {
19+
int elem = array[i];
20+
}
21+
```
22+
23+
In Rust we bundle this state and logic together into an object known as an
24+
"iterator".
25+
26+
<details>
27+
28+
- This slide provides context for what Rust iterators do under the hood. We use
29+
the (hopefully) familiar construct of a C-style `for` loop to show how
30+
iteration requires some state and some logic, that way on the next slide we
31+
can show how an iterator bundles these together.
32+
33+
- Rust doesn't have a C-style `for` loop, but we can express the same thing with
34+
`while`:
35+
```rust,editable
36+
let array = [2, 4, 6, 8];
37+
let mut i = 0;
38+
while i < array.len() {
39+
let elem = array[i];
40+
i += 1;
41+
}
42+
```
43+
44+
## More to Explore
45+
46+
There's another way to express array iteration using `for` in C and C++: You can
47+
use a pointer to the front and a pointer to the end of the array and then
48+
compare those pointers to determine when the loop should end.
49+
50+
```c,editable
51+
for (int *ptr = array; ptr < array + len; ptr += 1) {
52+
int elem = *ptr;
53+
}
54+
```
55+
56+
If students ask, you can point out that this is how Rust's slice and array
57+
iterators work under the hood (though implemented as a Rust iterator).
58+
59+
</details>

0 commit comments

Comments
 (0)