The problem: the same line, again and again
Programs often do the same thing to every item in a collection: greet each friend, print each fruit, add up each price. Without a loop, the only tool you have is to write the statement once per item.
Here is a vector of fruits and a program that prints a line for each one:
let fruits = vec!["banana", "orange", "pear", "kiwi"];
println!("I eat {}", fruits[0]);
println!("I eat {}", fruits[1]);
println!("I eat {}", fruits[2]);
println!("I eat {}", fruits[3]);
The output is:
I eat banana
I eat orange
I eat pear
I eat kiwi
It works, and for four fruits it is bearable. But look at what the four lines share: the same message, with only the index changing from 0 to 3. That shared part is a pattern, and a pattern repeated by hand is a sign that the language should be doing the repeating for you.
Three ways this goes wrong
It doesn't scale. With 100 fruits you would write 100 lines. With a vector whose size is only known when the program runs, for example one read from a file, you can't write the right number of lines at all.
It is easy to get wrong. Copying a line and editing the index invites slips: a repeated index prints one fruit twice and skips another, and the compiler won't notice, because both versions are valid code.
The index can be out of range. If the vector has three items and the code asks for fruits[3], Rust doesn't return a default value. The program panics while it runs:
index out of bounds: the len is 3 but the index is 3
Indexes start at 0, so a vector of length 3 has valid indexes 0, 1 and 2. Hand-written index lines make this kind of off-by-one error easy: change the vector and forget to change the lines, and the program breaks.
There is one more cost. If you decide to change the message from "I eat {}" to something else, you must edit every line, and missing one leaves an inconsistent output.
What a for loop gives you
A for loop lets you write the repeated statement once and have Rust run it for each item. The next lesson covers the syntax in full, but this is the shape of it:
for fruit in &fruits {
println!("I eat {}", fruit);
}
This prints the same four lines as before. Add a hundred more fruits to the vector and the loop doesn't change. Change the message and you edit one place. The loop also never asks for an index that doesn't exist, because it visits exactly the items that are there.
The exercise
This lesson asks you to feel the problem first, with no loop. The editor starts empty. The steps are:
- Declare
fruitswithletasvec!["banana", "orange", "pear", "kiwi"]. - Print
I eat FRUITfor each of the four fruits, one line per fruit, using onlyprintln!and reading each element by index,fruits[0]throughfruits[3].
Rust doesn't let you join a string literal and a &str with +, so the message can't be built as "I eat " + fruits[0]. Put a {} placeholder in the format string instead and pass the fruit as the next argument. Each {} is replaced by the next argument after the string. The reference solution is:
let fruits = vec!["banana", "orange", "pear", "kiwi"];
println!("I eat {}", fruits[0]);
println!("I eat {}", fruits[1]);
println!("I eat {}", fruits[2]);
println!("I eat {}", fruits[3]);
Four nearly identical lines is the cost you are meant to notice. Imagine this for a hundred fruits, and you can see what the loop in the next lesson removes.
Key takeaways
- When several statements differ only by an index, a loop should replace them.
- Writing one statement per item does not scale, invites typos, and breaks when the vector changes size.
- Indexing past the end of a
Vecpanics at run time instead of returning a default. - In
println!, each{}takes the next argument, so values are passed as arguments, not joined with+.
