user200783
user200783

Reputation: 14348

Is there a difference between `for i in &v` and `for i in v`?

I am currently reading chapter 8 of The Rust Programming Language, which contains this code:

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

I'm not sure why the & is necessary in for i in &v. I tried removing it and the code still works. Does for i in v do the same thing, or is it different somehow?

Upvotes: 3

Views: 399

Answers (2)

Masklinn
Masklinn

Reputation: 42492

I'm not sure why the & is necessary in for i in &v. I tried removing it and the code still works. Does for i in v do the same thing, or is it different somehow?

It does something slightly different (or it does the same thing on a different structure, if you prefer). The problem is at chapter 8 you're a bit early for those concepts, especially without knowing your experience with other programming languages.

I would strongly recommend that you keep on reading, and either you'll eventually find out (or understand on your own, I don't remember if it's made explicit) or you can come back to it later.

If it really bothers you, here's my attempt at an explanation:

As in various other programming programming languages, Rust's for loop works on arbitrary things which can be iterated (sometimes called "iterables"). In Rust the concept is represented by IntoIterator, aka "things which can be converted into an iterator". Now the "Into" prefix is important because it generally means consuming conversion (after the Into trait). That is,

let a = thing();
let b = a.into_iter();
// println!("{:?}", a); // probably an error because the previous line "consumed" `a` unless it's trivial and copy

So far so good. In one case Rust calls Vec::into_iter, and in another case it calls <&Vec>::into_iter. With many methods this would make no difference, because only one such method exists. However, it does matter for IntoIterator, because there exist both

impl<T> IntoIterator for Vec<T> 

and

impl<'a, T> IntoIterator for &'a Vec<T>

Why and what's the difference? Well, above I noted that IntoIterator consumes its subject, so the first consumes the Vec while the second just... consumes a reference to a Vec, which isn't even consuming because references can be copied.

As a result, in the original case you can keep using the Vec after having iterated it, but after your modification you can not, because the vector has been consumed and destroyed by the for loop. You can see that by adding

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

after the loop, the second one will refuse to build.

There's another consequence to this, which isn't too relevant here but is in other situations: if we look at the declaration of the two implementations more broadly we get this:

impl<T> IntoIterator for Vec<T> {
    type Item = T;

    [...]
}

impl<'a, T> IntoIterator for &'a Vec<T> {
    type Item = &'a T;

    [...]
}

The Item associated type is different: in the first case, because the vector is consumed the iterator gets to move (and provide) the actual items contained in the collection. In the second case however, since the vector is not consumed, the loop can not own the items: that'd be stealing and in Rust you can only steal from corpses. So it can only hand out references to the item.

This is mostly relevant for non-Copy types (not the case here as integers are Copy) as it influences the specific operations you can perform on the items (that is, in your loop).

Upvotes: 5

L. F.
L. F.

Reputation: 20619

(Please refer to the other comprehensive answer for details.)

Here's a simpler explanation.

As you learned before in the section Ways Variables and Data Interact: Move, assigning non-Copy variables result in a move rather than a copy. For example:

let a = String::from("hello");
let b = a;                     // move

println!("{} {}", a, b);       // error: borrow of moved value

Similarly, giving a Vec to a for loop transfers ownership to the loop. For Vec, each of the elements is moved into the loop variable, and the Vec is no longer accessible after the loop:

fn main() {
    // creates a Vec<String>
    let v: Vec<_> = ["a", "b", "c"].iter().copied().map(String::from).collect();

    // elements are moved into s, no clone required
    for s in v {
        println!("{}", s);
    }

    // error[E0382]: borrow of moved value: `v`
    // println!("{:?}", v);
}

(playground)

Passing a &Vec to a for loop, on the other hand, assigns the loop variable references to the elements, so the variable continues to own the Vec:

fn main() {
    // creates a Vec<String>
    let v: Vec<_> = ["a", "b", "c"].iter().copied().map(String::from).collect();

    // s is a reference to individual elements
    for s in &v {
        println!("{}", s);
    }

    // v can still be accessed
    println!("{:?}", v);
}

(playground)

Note that even though integers are Copy, a Vec containing them is not.

Upvotes: 2

Related Questions