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)>;
}
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.
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.