Introduction to Rust Programming πŸ¦€

Rust Basics πŸ¦€

Rust Basics Series #7: Using Loops in Rust

Loops are another way of handling control flow of your programs. Learn about for, while and 'loop' loops in Rust.

In the previous article of the Rust series, I went over the use of if and else keywords to handle the control flow of your Rust program.

That is one way of handling the control flow of your program. The other way you can do this is by using loops. So let us look at loops in this follow-up article.

Loops available in Rust

The Rust programming language has three different loops based on what you want to achieve and what is available:

  • for
  • while
  • loop

I presume that you are familiar with for and while but loop might be new here. Let's start with familiar concepts first.

The for loop

The for loop is primarily used to iterate over something called an iterator.

This iterator can be made from anything, from an array, a vector (will be covered soon!), a range of values, or anything custom. The sky is the limit here.

Let us look at the syntax of the for loop.

for iterating_variable in iterator {
    <statement(s)>;
}

The iterating_variable is more generally known as i in most other programming language tutorials ;)

And an iterator, as I said, can be really anything that tells what the next value is, if any.

Let's understand this using a program.

fn main() {
    let my_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

    println!("iteration over an array");
    for element in my_arr {
        println!("{}", element);
    }

    println!("\niteration over a real iterator");
    for element in my_arr.iter() {
        println!("{}", element);
    }

    println!("\nPython-style range");
    for element in 0..10 {
        println!("{}", element);
    }
}

Here, I have declared an array that holds 10 numbers, from 0 to 9. On the for loop that is on line 5, I simply specify this array as the iterator and Rust automatically handles iteration over all the elements of this array for me. No fancy my_arr[i] magic is needed.

But on line 10, I call the .iter() function on the array. This is an explicit mention of getting an iterator based on the values that my_arr consists of. The only difference between this loop and the loop on line 5 is that here you are being explicit by calling the .iter() function on the array.

Calling the .iter() function on a data type, in this context, isn't strictly necessary. Since this is an array, which is a data type provided by the language itself, Rust already knows how to handle it. But you will need it with custom data types.

Finally, on line 15, we have a for loop that loops over a range. Well sort of. If you look closely, this range will look very similar to the Slice "type". Rust knows about this too and handles iteration for you (haha, get it?).

The output looks like following:

iteration over an array
0
1
2
3
4
5
6
7
8
9

iteration over a real iterator
0
1
2
3
4
5
6
7
8
9

Python-style range
0
1
2
3
4
5
6
7
8
9

The while loop

The while loop can be thought to be very similar to an if conditional statement. With the if statement, provided that the user-provided condition evaluates to true, the code in the if statement's body is executed once.

But with the while loop, if the condition evaluates to true, the loop starts looping over the loop body. The loop will continue its iteration as long as the condition keeps on evaulating to true.

The while loop stops only when the loop has completed the execution of all statements in the current iteration and upon checking the condition, it evaluates to false.

Let's look at the syntax of a while loop...

while condition {
    <statement(s)>;
}

See? Very similar to an if conditional statement! No else blocks though ;)

Let's look at a program to understand this better.

fn main() {
    let mut var = 0;

    while var < 3 {
        println!("{var}");
        var += 1;
    }
}

I have a mutable variable, var, with an initial value of 0. The while loop will loop as long as the value stored in the mutable variable var is less than 3.

Inside the loop, var's value gets printed and later on, its value gets incremented by 1.

Below is the output of the code written above:

0
1
2

The loop

Rust has an infinite loop. Yes, one with no condition for starting and no condition to stop. It just continues looping over and over again till infinity. But of course, has triggers to stop the loop execution from the code itself.

The syntax for this infinite loop is as follows:

loop {
    <statement(s)>;
}
πŸ“‹
These loops are mostly used in GUI software where exiting is an explicit operation.

Before I even give you an example, since this loop is quite special, let's first look at how to exit it :p

To stop the execution of an infinite loop, the break keyword is used inside the loop.

Let's look at an example where only whole numbers between 0 and 3 (inclusive) are printed to the program output.

fn main() {
    let mut var = 0;

    loop {
        if var > 3 {
            break;
        }

        println!("{}", var);
        var += 1;
    }
}

The best way to interpret this particular example is to look at it as an unnecessarily expanded form of a while loop ;)

You have a mutable variable var with an initial value of 0 that is used as an iterator, kind of. The infinite loop starts with an if condition that should var's value be greater than 3, the break keyword should be executed. Later on, like the previous example of the while loop, var's value is printed to the stdout and then its value is incremented by 1.

It produces the following output:

0
1
2
3

Labelled loops

Let's say there are two infinite loops, one nested in the other. For some reason, the exit condition is checked in the innermost loop but this exit condition is for exiting out of the outermost loop.

In such a case, labelling the loop(s) might be beneficial.

πŸ’‘
The use of labels break and continue keywords are not exclusive to the infinite loop. They can be used with all three loops that the Rust language offers.

Following is how to label a loop.

'label: loop {}

To tell the compiler that a loop is being labelled, start with a single quote character, type the label for it, and follow it with a colon. Then, continue with how you regularly define a loop.

When you need to break certain loop, simply specify the loop label like so:

    break 'label;

Let's take a look at an example to better understand this.

fn main() {
    let mut a = 0;
    let mut b = 0;

    'parent: loop {
        a += 1;

        loop {
            println!("a: {}, b: {}", a, b);
            b += 1;

            if a + b == 10 {
                println!("\n{} + {} = 10", a, b);
                break 'parent;
            }
        }
    }
}

Here, I took two mutable variables a and b with the initial values set to 0 for both.

Later down, the outer-most loop is labelled parent. The 'parent' loop increments the value of varaible a by 1 and has an inner/child loop.

This child loop (on line 8) prints the values of variables a and b. Inside this loop, the value of b gets incremented by 1. And the exit condition is that a + b == 10. Meaning whenever the values stored in variables a and b, when added together, result in 10, the parent loop is broken. Even though the break condition on line 14 "belongs" to the inner loop, it breaks the parent loop.

Let's look at the program output now.

a: 1, b: 0
a: 1, b: 1
a: 1, b: 2
a: 1, b: 3
a: 1, b: 4
a: 1, b: 5
a: 1, b: 6
a: 1, b: 7
a: 1, b: 8

1 + 9 = 10

As evident from the program output, the loop stops as soon as a and b have the values 1 and 9 respectively.

The continue keyword

If you have already used loops in any other programming language like C/C++/Java/Python, you might already know the use of the continue keyword.

While the break keyword is to stop the loop execution completely, the continue keyword is used to "skip" the current iteration of loop execution and start with the next iteration (if the conditions permit).

Let's look at an example to understand how the continue keyword works.

fn main() {
    for i in 0..10 {
        if i % 2 == 0 {
            continue;
        }
        println!("{}", i)
    }
}

In the code above, I have a for loop that iterates over whole numbers between 0 and 9 (inclusive). As soon as the loop starts, I put a conditional check to see if the number is even or not. If the number is even, the continue keyword is executed.

But if the number is odd, the number gets printed to the program output.

Let's first look at the output of this program.

1
3
5
7
9

As you can see, the loop appears to have been "going on" even though there clearly are even numbers between 0 and 9. But because I used the continue keyword, the loop execution stopped when that keyword was encountered.

The loop skipped whatever was under it and continued with the next iteration. That's why even numbers are not printed, but all odd numbers between 0 and 9 are printed to the proogram output.

Conclusion

To conclude this long article, I demonstrated the use of 3 different loops: for, while and loop. I also discussed two keywords that affect the control flow of these loops: break and continue.

I hope that you now understand the appropriate use case for each loop. Please let me know if you have any questions.