Part 2. Iteration (5 points)

Iteration with iterators is a key concept in Rust. Iteration is similar to Java but has some quirks which can be quite surprising.

For the rest of this lab, you’ll explore iterating over elements in a vector and iterating over characters in a string.

Your task

In a shell, cd to your assignment repository and create a new project using cargo and then open it in VS Code.

$ cargo new iteration
$ code iteration

Change the main function to

fn main() {
    let data = vec![2, 3, 5, 7, 11, 13];

    for x in data {
        println!("{x}");
    }
}

Tip

Hover your mouse over the code above and some buttons will appear in the top right of the text box. Click the Run button to run this code.

Info

The vec! macro creates a new Vec containing the elements in the brackets. (Click the link in the previous sentence to go to the documentation for Vec.) Vec is the standard vector class in Rust: it is a growable array of elements, similar to a list in Python and an ArrayList in Java. The vec! macro here gives a result similar to this (but more efficient and easier to read).

    let mut data = Vec::new(); // Create a new Vec
    data.push(2);  // Add 2 to the end of the Vec.
    data.push(3);  // Add 3 to the end of the Vec.
    // ...
    data.push(13); // Add 13 to the end of the Vec.

Predict what the output of the code will be and then run it. If you predicted incorrectly, try to figure out why the code does what it does before moving on.

Now, duplicate the for loop

fn main() {
    let data = vec![2, 3, 5, 7, 11, 13];

    for x in data {
        println!("{x}");
    }

    for x in data {
        println!("{x}");
    }
}

Again, predict what the output of the code will be and then run it. (You can click the Run button on the above text box to see the error message.)

I suspect the results are quite surprising. You probably got an error that looks like this.

error[E0382]: use of moved value: `data`
   --> src/main.rs:8:14
    |
2   |     let data = vec![2, 3, 5, 7, 11, 13];
    |         ---- move occurs because `data` has type `Vec<i32>`, which does not implement the `Copy` trait
3   |
4   |     for x in data {
    |              ---- `data` moved due to this implicit call to `.into_iter()`
...
8   |     for x in data {
    |              ^^^^ value used here after move
    |
note: `into_iter` takes ownership of the receiver `self`, which moves `data`
   --> /Users/steve/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/iter/traits/collect.rs:262:18
    |
262 |     fn into_iter(self) -> Self::IntoIter;
    |                  ^^^^
help: consider iterating over a slice of the `Vec<i32>`'s content to avoid moving into the `for` loop
    |
4   |     for x in &data {
    |              +

For more information about this error, try `rustc --explain E0382`.
error: could not compile `iteration` (bin "iteration") due to previous error

This is a lot of information and it’s quite difficult to understand at first. Reading error messages takes practice. If you look carefully, you’ll notice that the error message is divided into three parts, the error, a note, and a help.

The error tells us that we have tried to use a moved value, namely data on line 8, column 14 of the file src/main.rs. Next, it shows that on line 2 the variable data has type std::vec::Vec<i32> which does not implement Copy. Then, on line 4, data was moved due an implicit call to .into_iter() and finally, on line 8, we tried to use data again.

We’ll talk about what this all means but basically, the first for loop used up or consumed our Vec and thus the Vec no longer exists. This is not how we normally expect for loops to work. Nevertheless, this is how Rust works.

The note portion of the error message tells us why it was used up and shows us code from the standard library to explain why. Let’s skip this part for now.

Finally, the help portion offers a way to solve this problem. If we use for x in &data { } (note the addition of the ampersand &), this will avoid consuming data. Note that it suggests making this change only on the first for loop (which for me happens to be on line 4). By using &data rather than data, we’re instructing the loop to iterate over a reference to the Vec, rather than the Vec itself. The upshot is that we can now iterate multiple times.

Go ahead and make that change and rerun cargo run. At this point, your code should print out the list twice.