What a for loop does
A for loop runs a block of code once for each item of a collection. You don't count positions or check where the collection ends. Rust hands you the items one at a time, in order, and stops after the last.
Looping over the characters of a string
A string is a sequence of characters, so a loop can visit each one:
let name = "Andy";
for letter in name.chars() {
println!("{}", letter);
}
The output is:
A
n
d
y
The pieces of the loop are:
forstarts the loop.letteris the loop variable. On each pass it holds the next item. You choose its name.inseparates the loop variable from the thing being walked over.name.chars()produces the characters of the string one by one.- The braces enclose the body, the code that runs on every pass.
Rust asks for .chars() here because a string can be viewed as bytes or as characters, and the choice is yours to make. Looping over name.chars() gives characters of type char. You can't write for letter in name directly, since a &str is not itself iterable.
Looping over a vector
The same shape works for a Vec:
let fruits = vec!["banana", "orange", "pear", "kiwi"];
for fruit in &fruits {
println!("I eat {}", fruit);
}
This prints:
I eat banana
I eat orange
I eat pear
I eat kiwi
On the first pass fruit is "banana", on the second "orange", and so on. The body uses fruit to build a different message each time, which is exactly what the hand-written version could not do without repeating itself.
Why the ampersand matters
The loop is written over &fruits, not fruits. The & borrows the vector for the duration of the loop. Without it, the loop takes ownership of the vector and consumes it. The vector is then moved into the loop and can't be used after it. This program:
let friends = vec!["Andy", "Mary", "John"];
for friend in friends {
println!("{}", friend);
}
println!("{:?}", friends);
doesn't compile. The compiler reports borrow of moved value: `friends` and suggests iterating over a slice of the vector's content, that is, adding the &. Writing for friend in &friends leaves the vector usable after the loop. For a fixed-size array such as [1, 2, 3] the plain form also compiles, because arrays of copyable numbers can be copied, but borrowing is the habit that works for vectors.
When you also need the position
A plain loop doesn't tell you which pass you are on. If you need the position as well, wrap the iterator in enumerate(), which yields pairs of an index and an item:
for (i, friend) in friends.iter().enumerate() {
println!("{} {}", i, friend);
}
With the friends above this prints 0 Andy, 1 Mary and 2 John. The lesson's exercise doesn't need the index, so the plain loop is the better choice there: fewer moving parts, and no index to get wrong.
The exercise
The editor starts with a vector of friends:
let friends = vec!["Andy", "Mary", "John"];
The task is to greet each friend, in two steps.
Step 1. Write a for...in loop whose loop variable is named friend and which iterates over &friends.
Step 2. Inside the loop, print Hey, , the name and !. Use the format string "Hey, {}!" and pass friend as the argument that fills the {}.
The reference solution is:
let friends = vec!["Andy", "Mary", "John"];
for friend in &friends {
println!("Hey, {}!", friend);
}
It prints Hey, Andy!, Hey, Mary! and Hey, John!, each on its own line. The body runs three times because the vector has three items.
Common mistakes
- Leaving out the
&and then using the vector after the loop. - Calling the loop variable one thing and using another in the body. Inside the loop only
friendexists, so writingfriendsthere would give you the whole vector, which{}can't print. - Using
for x in stringwithout.chars(). - Putting the print after the closing brace. It then runs once, not once per item, and the loop variable is out of scope there.
Key takeaways
for item in collectionruns its body once for every item, in order.- Use
text.chars()to walk the characters of a string. - Use
&vecto loop over a vector without giving it up. - Each
{}inprintln!is filled by the next argument.
