Reputation: 14348
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
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
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);
}
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);
}
Note that even though integers are Copy
, a Vec
containing them is not.
Upvotes: 2