diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 6e4456e4..47e81978 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -171,9 +171,11 @@ - [Welcome](welcome-day-4.md) - [Iterators](iterators.md) - - [`Iterator`](iterators/iterator.md) - - [`IntoIterator`](iterators/intoiterator.md) + - [Motivation](iterators/motivation.md) + - [`Iterator` Trait](iterators/iterator.md) + - [`Iterator` Helper Methods](iterators/helpers.md) - [`collect`](iterators/collect.md) + - [`IntoIterator`](iterators/intoiterator.md) - [Exercise: Iterator Method Chaining](iterators/exercise.md) - [Solution](iterators/solution.md) - [Modules](modules.md) diff --git a/src/iterators/helpers.md b/src/iterators/helpers.md new file mode 100644 index 00000000..5ea5f4c2 --- /dev/null +++ b/src/iterators/helpers.md @@ -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); +``` + +
+ +- 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. + +
diff --git a/src/iterators/intoiterator.md b/src/iterators/intoiterator.md index aeb22c24..631ca0bd 100644 --- a/src/iterators/intoiterator.md +++ b/src/iterators/intoiterator.md @@ -57,6 +57,11 @@ fn main() {
+- `IntoIterator` is the trait that makes for loops work. It is implemented by + collection types such as `Vec` and references to them such as `&Vec` 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 `IntoIterator` must declare two types: diff --git a/src/iterators/iterator.md b/src/iterators/iterator.md index d0b7c072..0d370794 100644 --- a/src/iterators/iterator.md +++ b/src/iterators/iterator.md @@ -2,49 +2,67 @@ minutes: 5 --- -# `Iterator` +# `Iterator` Trait -The [`Iterator`][1] trait supports iterating over values in a collection. It -requires a `next` method and provides lots of methods. Many standard library -types implement `Iterator`, and you can implement it yourself, too: +The [`Iterator`][1] trait defines how an object can be used to produce a +sequence of values. For example, if we wanted to create an iterator that can +produce the elements of a slice it might look something like this: ```rust,editable -struct Fibonacci { - curr: u32, - next: u32, +struct SliceIter<'s> { + slice: &'s [i32], + i: usize, } -impl Iterator for Fibonacci { - type Item = u32; +impl<'s> Iterator for SliceIter<'s> { + type Item = &'s i32; fn next(&mut self) -> Option { - let new_next = self.curr + self.next; - self.curr = self.next; - self.next = new_next; - Some(self.curr) + if self.i == self.slice.len() { + None + } else { + let next = &self.slice[self.i]; + self.i += 1; + Some(next) + } } } fn main() { - let fib = Fibonacci { curr: 0, next: 1 }; - for (i, n) in fib.enumerate().take(5) { - println!("fib({i}): {n}"); + let slice = [2, 4, 6, 8].as_slice(); + let iter = SliceIter { slice, i: 0 }; + for elem in iter { + println!("elem: {elem}"); } } ```
-- 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. In Rust these functions - should produce the code as efficient as equivalent imperative implementations. +- The `SliceIter` example implements the same logic as the C-style `for` loop + demonstrated on the last slide. -- `IntoIterator` is the trait that makes for loops work. It is implemented by - collection types such as `Vec` and references to them such as `&Vec` 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. +- Point out to the students that iterators are lazy: Creating the iterator just + initializes the struct but does not otherwise do any work. No work happens + until the `next` method is called. + +- 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]`).
[1]: https://doc.rust-lang.org/std/iter/trait.Iterator.html +[2]: https://doc.rust-lang.org/stable/std/slice/struct.Iter.html diff --git a/src/iterators/motivation.md b/src/iterators/motivation.md new file mode 100644 index 00000000..812dca8b --- /dev/null +++ b/src/iterators/motivation.md @@ -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". + +
+ +- 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). + +