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}"); } }
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.
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.
You can think of this as similar to how shift
works in Bash. Recall that shift
is used for positional parameters; when we call it, it removes the first positional parameter entirely, and shifts the rest of the parameters up by 1. If we use shift
to loop through every positional parameter, by the end of the loop, those parameters will be gone, and we cannot access them again. This is what’s happening in our Rust code - by the end of our loop, every item in our Vec
is gone, so we can’t access them again in a subsequent loop.
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.
References do not own data; they simply point to data owned by another variable. A reference is simply the address in memory where we can find the data. The reference tells the for
loop that it’s not allowed to consume the data, because the reference doesn’t own the data.
The upshot to using a reference here 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.