Start learning free

LESSON 114 OF 414 · FOR LOOPS - LOOPING OVER ARRAYS

Why For Loops in Rust: Repeating Work Over a Vec

Lesson Read the full explanation

For loops are very useful for iterating over sequences of elements.

To understand the value of for loops, let's take some time to see how frustrating our life would be if a repeated task required us to type out the same code every single time.


Example: Greeting our friends

Imagine this: We have a vector of friends and we want to greet each of them using a message template.

If we only use println!, our program might look like this:

let friends = vec!["Andy", "Mircea", "David", "Mary"];

println!("Hey, {}", friends[0]);
println!("Hey, {}", friends[1]);
println!("Hey, {}", friends[2]);
println!("Hey, {}", friends[3]);

That's ugly, but still manageable. After all, we're writing only 4 lines of code and most probably copying and pasting a few times.

Now imagine if we had 10, 100 friends? It would take an extremely long time and by the end, we could still end up with inconsistencies and mistakes.

We'll learn how for loops come to our rescue in the next lesson. But for now, let's gain an appreciation for them.


Assignment
Follow the Coding Tutorial to see how frustrating our life is without for loops.


Hint
Look at the examples above if you get stuck.


STEP 1 OF 3
    Ctrl + Enter to check

    RUST · FOR LOOPS - LOOPING OVER ARRAYS

    Why For Loops in Rust, explained

    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:

    1. Declare fruits with let as vec!["banana", "orange", "pear", "kiwi"].
    2. Print I eat FRUIT for each of the four fruits, one line per fruit, using only println! and reading each element by index, fruits[0] through fruits[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 Vec panics at run time instead of returning a default.
    • In println!, each {} takes the next argument, so values are passed as arguments, not joined with +.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free