I also blog frequently on the Yesod Web Framework blog, as well as the FP Complete blog.

Crates and more iterators - Rust Crash Course lesson 4 - exercise solutions

See a typo? Have a suggestion? Edit this page on Github

Below are the solutions to the exercises from the last Rust Crash Course lesson, “Crates, I/O, and more iterators.”

This post is part of a series based on teaching Rust at FP Complete. If you’re reading this post outside of the blog, you can find links to all posts in the series at the top of the introduction post. You can also subscribe to the RSS feed.

Exercise 1

You can find my complete solution as a Github Gist. If your solution looks a bit different than mine, don’t worry. Also, see if there’s anything interesting you can learn from my implementation, or some improvements you’d like to make to it.

Exercise 2

struct TheAnswer;

impl Iterator for TheAnswer {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        Some(42)
    }
}

Exercise 3

Let’s start with the simpler solution:

struct Fibs {
    x: u32,
    y: u32,
}

fn fibs() -> Fibs {
    Fibs {
        x: 0,
        y: 1,
    }
}

impl Iterator for Fibs {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        let orig_x = self.x;
        let orig_y = self.y;

        self.x = orig_y;
        self.y = orig_x + orig_y;

        Some(orig_x)
    }
}

fn main() {
    for i in fibs().take(10) {
        println!("{}", i);
    }
}

However, if you bump that take(10) to take(47), the end of your output will look like:

701408733
1134903170
thread 'main' panicked at 'attempt to add with overflow', foo.rs:21:18
note: Run with `RUST_BACKTRACE=1` for a backtrace.

One solution would be to bump to a u64, but that’s just delaying the problem. Instead, we can use Rust’s checked addition method:

fn next(&mut self) -> Option<u32> {
    let orig_x = self.x;
    let orig_y = self.y;

    match orig_x.checked_add(orig_y) {
        // overflow
        None => None,

        // no overflow
        Some(new_y) => {
            self.x = orig_y;
            self.y = new_y;

            Some(orig_x)
        }
    }
}

Now our stream will stop as soon as overflow occurs.

If you want to get really advanced here, you could actually output two more values. To do so, we need to assign to a derefenced value and use an enum to track our state:

fn next(&mut self) -> Option<u32> {
    use Fibs::*;
    match *self {
        Done => None,
        OneLeft(x) => {
            *self = Done;
            Some(x)
        }
        Running(orig_x, orig_y) => {
            *self = match orig_x.checked_add(orig_y) {
                // overflow
                None => OneLeft(orig_y),
                Some(new_y) => Running(orig_y, new_y),
            };

            Some(orig_x)
        }
    }
}

Exercise 4

impl<I> Iterator for Doubler<I>
    where
    I: Iterator,
    I::Item: std::ops::Add<Output=I::Item> + Copy,
{
    type Item = I::Item;
    fn next(&mut self) -> Option<Self::Item> {
        match self.iter.next() {
            None => None,
            Some(x) => Some(x + x),
        }
    }
}

Exercise 5

The fold method takes two parameters: the initial value, and a function for adding the running total with the next value. One approach using closures is:

fn main() {
    let res = (1..11).fold(0, |x, y| x + y);
    println!("{}", res);
}

Another approach is to directly refer to the addition function. Remember how there was a Mul trait for the * operator? There’s also an Add trait for addition:

fn main() {
    let res = (1..11).fold(0, std::ops::Add::add);
    println!("{}", res);
}

As for writing our own sum function: we’ll end up back in the situation where things are generic and we have to provide appropriate traits. We’ll follow a similar approach with using From and a u8:

fn sum<I>(iter: I) -> I::Item
    where
    I: Iterator,
    I::Item: std::ops::Add<Output=I::Item> + From<u8>,
{
    iter.fold(From::from(0u8), std::ops::Add::add)
}

Rust at FP Complete | Introduction

Blog archive