Why is iterating over a collection via `for` loop considered a "move" in Rust?
Asked Answered
J

2

12

I have the below Rust program.

fn main() {
    let v = vec![100, 32, 57];
    for i in v {
        println!("{}", i);
    }

    println!("{:?}", v);
}

When I run it, I get:

error[E0382]: borrow of moved value: `v`
 --> src\main.rs:7:22
  |
2 |     let v = vec![100, 32, 57];
  |         - move occurs because `v` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
3 |     for i in v {
  |              -
  |              |
  |              value moved here
  |              help: consider borrowing to avoid moving into the for loop: `&v`
...
7 |     println!("{:?}", v);
  |                      ^ value borrowed here after move

The error states that there is a move happened at for i in v. But I'm just using the same variable v defined by let v = vec![100, 32, 57]. It's not something like let v2 = v; for i in v2 ..., which moved the value from v to v2. Could anyone help to explain a little bit?

Jilleen answered 1/12, 2019 at 7:27 Comment(0)
T
18

As https://doc.rust-lang.org/reference/expressions/loop-expr.html#iterator-loops says,

A for expression is a syntactic construct for looping over elements provided by an implementation of std::iter::IntoIterator.

Vec implements IntoIterator, allowing you to own a Vec instance’s elements by consuming it:

Creates a consuming iterator, that is, one that moves each value out of the vector (from start to end). The vector cannot be used after calling this.

(As the error message notes, the way to fix this is to loop over &v instead of v, borrowing its elements instead of owning them. You can loop for &i in &v to maintain the type of i.)

It might seem unnecessary at a high level for you to own the elements of v, since they’re copyable, but there’s no special implementation allowing that information to be used here. IntoIterator.into_iter() takes self, meaning a for loop always consumes the value being iterated over.

Thistly answered 1/12, 2019 at 7:54 Comment(0)
O
1

As mentioned in other answers, for item in container { ... } calls container.into_iter() which consumes the container (taking it by value) and returns an IntoIter iterator that owns the items until the loop is done.

One way to loop over the items without consuming them is to use a reference to the container:

let container: Vec<String> = vec!["Hello".to_string(), "World".to_string()];
for item in &container {
    println!("item: {item}");
}
println!("container: {container:?}");

another way is to use for item in container.iter() { ... }, calling iter() instead of the implicit into_iter() method:

let container: Vec<String> = vec!["Hello".to_string(), "World".to_string()];
for item in container.iter() {
    println!("item: {item}");
}
println!("container: {container:?}");
Outrigger answered 18/4, 2023 at 17:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.