29

If I try to iterate over a slice twice, it works fine:

let a = &[1, 2, 3];
for i in a {
    println!("{}", i);
}
for i in a {            // works fine
    println!("{}", i);
}

If I try to iterate over a vector twice, it fails:

let a = vec![1, 2, 3];
for i in a {
    println!("{}", i);
}
for i in a {
    println!("{}", i);
}
error[E0382]: use of moved value: `a`
 --> src/main.rs:6:14
  |
3 |     for i in a {
  |              - value moved here
...
6 |     for i in a {
  |              ^ value used here after move
  |
  = note: move occurs because `a` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait

I see that the IntoIterator trait takes self by value, so it makes sense to me that the second example fails. Why does the first example succeed?

3
  • 3
    Pedantically, in the first case, the type of a isn't actually a slice, it's a reference to an array of length 3. However, deref coercions allow a reference to an array to act like a slice in most cases.
    – Shepmaster
    Commented Jan 3, 2016 at 2:51
  • @Shepmaster thanks for clarifying. Am I right in thinking that the type of a in the first example is &[i32; 3], while a slice would be &[i32]? Also, is the deref coercion you mentioned visible in the list here, or is it more magical? Commented Jan 3, 2016 at 8:03
  • 1
    Yes, those would be the types. I lied a little bit, it is a bit more magical than a dereference, it's a coercion. The docs will be updated soonish with the fix.
    – Shepmaster
    Commented Jan 3, 2016 at 15:33

1 Answer 1

45

Like you said, for works by taking the thing you asked it to iterate over, and passing it through IntoIterator::into_iter to produce the actual iterator value. Also as you said, into_iter takes the subject by value.

So, when you try to iterate over a Vector directly, this means you pass the entire vector, by value, into its IntoIterator implementation, thus consuming the vector in the process. Which is why you can't iterate over a vector directly twice: iterating over it the first time consumes it, after which it no longer exists.

However, slices are different: a slice is an immutable, borrowed pointer to its data; immutable, borrowed pointers can be copied freely. This means that the IntoIterator for immutable slices just borrows the data and doesn't consume it (not that it could). Or, to look at it another way, its IntoIterator implementation is simply taking a copy of the slice, whereas you can't copy a Vec.

It should be noted that you can iterate over a Vec without consuming it by iterating over a borrow. If you check the documentation for Vec, you'll note that it lists implementations of IntoIterator for Vec<T>, &Vec<T> and &mut Vec<T>.

let mut a: Vec<i32> = vec![1, 2, 3];

for i in &a {           // iterate immutably
    let i: &i32 = i;    // elements are immutable pointers
    println!("{}", i);
}

for i in &mut a {       // iterate mutably
    let i: &mut i32 = i;// elements are mutable pointers
    *i *= 2;
}

for i in a {            // iterate by-value
    let i: i32 = i;     // elements are values
    println!("{}", i);
}

// `a` no longer exists; it was consumed by the previous loop.
5
  • This was super helpful, thanks. Can I summarize it this way? 1) If a type derives Copy, you can pass it through a "by value" function and still use it afterwards, and 2) in some magical way, slices implement Copy. Commented Jan 3, 2016 at 6:12
  • 2
    @JackO'Connor, that's basically correct, except there is really nothing magical in that slices implement Copy. A slice is essentially an immutable reference, and immutable references are naturally Copy. Commented Jan 3, 2016 at 11:49
  • 1
    Is the same result as in your code could be achieved the same by using iter_mut and into_iter?
    – Tiina
    Commented Apr 28, 2021 at 3:05
  • Can you explain, why in the first two examples i needs to be replaced by a borrow from i? I didn't now that and that caused me hours of playing around with &, * and mut at all kind of positions. Commented Dec 12, 2023 at 6:50
  • @FordPrefect It's not. That is demonstrating that i in the first loop is a &i32 (not an i32), and is a &mut i32 in the second. If the types didn't line up, there would be a type error. It's probably written that way because you can't directly specify the type of a binding in a for loop.
    – DK.
    Commented Dec 14, 2023 at 2:46

Not the answer you're looking for? Browse other questions tagged or ask your own question.