In this article I'll discuss the main program flow aspects of any programming language, the conditions
and loops
and how they are implemented in Rust.
β οΈ Remember!
You can find all the code snippets for this series in its accompanying repo
If you don't want to install Rust locally, you can play with all the code of this series in the official Rust Playground that can be found on its official page. (cargo commands won't run though. But you mostly won't be needing it)
β οΈβ οΈ The articles in this series are loosely following the contents of "The Rust Programming Language, 2nd Edition" by Steve Klabnik and Carol Nichols in a way that reflects my understanding from a Python developer's perspective.
β I try to publish a new article every week (maybe more if the Rust gods π are generous π) so stay tuned π. I'll be posting "new articles updates" on my LinkedIn and Twitter.
Table of Content:
Conditions:
Nothing to see here, moving on to loops
... Naah! Just kidding π
Rust has several condition-checking statements. The most famous is the if
statement which we will cover here. Later in the series we will discover match
, a statement used for "pattern matching".
The if statement:
Rust has the standard if
statement like Python and the other programming languages. I'm assuming that you already know how to code so I won't go into details such as Boolean values, logical operators and comparison operators.
The if
statement in Rust has the following familiar structure:
fn main() {
let age: u8 = 29;
if age < 30 {
println!("Age is below 30");
} else if age > 30 {
println!("Age is above 30");
} else {
println!("Age is equal to 30");
}
}
If you know JavaScript, this structure will be very familiar to you. And it functions as expected too. Except for one small detail π€ ... the condition part must be a Boolean
! No implicit conversions, no type guessing, no nothing! If the condition isn't a Boolean, the program will "panic" . So, the following will fail:
fn main() {
let age: u8 = 29;
if age {
println!("Age is not equal to zero");
} else {
println!("Age is equal to zero");
}
}
Instead, you rewrite the statement to show our explicit intention to compare age
to 0
:
fn main() {
let age: u8 = 29;
if age != 0 {
println!("Age is not equal to zero");
} else {
println!("Age is equal to zero");
}
}
π¦ New Rust Terminology: "panic" means that the program has errored out!
Using if with let:
Sometime you may want to "conditionally" set a variable. You can do that combining if
and let
in the same statement (remember ... statements and expressions from the last article?). Because blocks in Rust can return their last value, the following is a perfectly legal statement (notice the absence of a semi-colon at the end of each block):
fn main() {
let thershold = 50;
let value = 10;
let effective_value = if value > thershold {
value - thershold // No semi-colon
} else {
0 // No semi-colon
};
println!("Effective value is {effective_value}")
}
This will print the following in stdout:
Effective value is 0
(Play with the value of value
to get different outputs)
But this functionality comes with a caveat (you might have guessed it by now π). Both if
branches must have the same type! So, the following will fail
let threshold = 50;
let value = 10;
let effective_value = if value > threshold { // Error: expected integer, found '&str'
value - threshold
} else {
"zero"
};
Because value
is the default integer type (i32) and so as threshold
, the return value of value - threshold
is also an i32
. But for the else
branch it returns a "string literal reference" which will confuse the Rust compiler and causes the program to "panic".
π¦ New Rust Terminology: Rust calls the hard-coded strings in the code as "string literals" and they are used inside a Rust program as "references". I don't expect you to know what that means π so stay tuned for future articles.
Loops:
The other aspect of the program flow control is loops. Again, as I assume you are already a Rock Star coder, I won't discuss what loops are and how they work. Instead, I'll go just explore loops in Rust.
The loop keyword:
Rust has the loop
keyword that technically presents an "infinity-loop" inside your program, so running the following code will freeze your terminal unless you press ctrl/cmd + c
:
fn main() {
loop {}
}
And as in Python, Rust has the break
keyword that "breaks out of a loop" and the continue
keyword that "skips" the current loop iteration and starts the next one.
fn main() {
loop {
println!("Enter loop");
break;
println!("After break");
}
println!("Outside the loop");
}
The output of the previous code will be:
Enter loop
Outside the loop
The loop is executed only once (hence the Enter loop
) then the break
keyword exited the loop before hitting println!("After break");
and the second Outside the loop
is printed out.
Also using the continue
keyword as follows, will produce another "infinity-loop" as the break
keyword is "unreachable".
fn main() {
// Will never break!
loop {
continue;
break; // Unreachable
}
}
As you have seen by now that anything in Rust that uses code blocks "{}", can return values and the loop
keyword isn't an exception to that! You can use break <value>
to break out of the loop and return this value.
fn main() {
// Returning values from loop
let mut students_count = 0;
let class_capacity = loop {
students_count += 1;
if students_count >= 30 {
break students_count;
}
};
println!("The class capacity is {class_capacity}")
}
This code will output:
The class capacity is 30
As we saw with if
and let
, loop
also can be used with let
. In this code, the loop will break at students_count >= 30
and returns 30
to class_capacity
.
A Rust program can have "nested" loop
keywords. But this isn't a pretty sight! Luckily, you can name loops to gain some control. For example:
fn main() {
let mut base_count_down = 3;
'count_down: loop {
let mut base_count_up = 0;
'count_up: loop {
base_count_up += 1;
if base_count_up > 10 {
break; // breaks from "count_up loop"
}
if base_count_down == 0 {
break 'count_down; // break from "count_down loop"
}
println!("Up: {}, Down: {}", base_count_up, base_count_down);
}
base_count_down -= 1;
}
println!("Loops are terminated!")
}
This will produce
Up: 1, Down: 3
Up: 2, Down: 3
Up: 3, Down: 3
Up: 4, Down: 3
Up: 5, Down: 3
Up: 6, Down: 3
Up: 7, Down: 3
Up: 8, Down: 3
Up: 9, Down: 3
Up: 10, Down: 3
Up: 1, Down: 2
Up: 2, Down: 2
Up: 3, Down: 2
Up: 4, Down: 2
Up: 5, Down: 2
Up: 6, Down: 2
Up: 7, Down: 2
Up: 8, Down: 2
Up: 9, Down: 2
Up: 10, Down: 2
Up: 1, Down: 1
Up: 2, Down: 1
Up: 3, Down: 1
Up: 4, Down: 1
Up: 5, Down: 1
Up: 6, Down: 1
Up: 7, Down: 1
Up: 8, Down: 1
Up: 9, Down: 1
Up: 10, Down: 1
Loops are terminated!
As you can see, the count_up
loop is counting from 1 to 10 and the count_down
loop is count from 3 to 1.
β οΈ You may notice that the
base_count_up
variable is being defined at eachcount_up
loop iteration. This is legal in Rust as it uses a Rust feature called "shadowing" which we will talk about in future articles.
Conditional loops with while:
while
in Rust works as you expect as in Python and in any other language. The loop will execute provided that the condition is true
. And like the if
statement, the while
condition must be of a Boolean type:
fn main() {
let mut number = 10;
while number != 0 {
println!("The number is {number}");
number -= 1;
}
println!("Outside of while!")
}
The output of the preceding code is:
The number is 10
The number is 9
The number is 8
The number is 7
The number is 6
The number is 5
The number is 4
The number is 3
The number is 2
The number is 1
Outside of while!
Looping with for:
And last but not least, the friendly neighborhood for
loop π
This too works as you expect like in any language and you can use for
to iterate over Rust "collections" (like arrays and tuples).
fn main() {
// For loops
let arr = [1, 2, 3, 10, 9, 8];
for element in arr {
println!("The array value is {element}")
}
}
This will produce the following:
The array value is 1
The array value is 2
The array value is 3
The array value is 10
The array value is 9
The array value is 8
β οΈ We could have just obtained the same result using
while
and anindex
mutable variable. But we should know the exact array length as out-of-range indexes will cause a Rust program to "panic"
And like Python, you can use a Rust Range
to loop a predefined number of times. The below code counts from 10 to 1.
fn main() {
// count-down loop
for num in (1..11).rev() {
println!("Count-down {num}")
}
println!("Go! Go! Go!")
}
That's a quick overview of the program flow control in Rust! In the next article, we will put everything together and build our first functional Rust application π€©. See you then π
Top comments (2)
Sort of. It means the program has errored out at runtime. But the examples you give will not panic because they will instead error at compile time. Compiler errors are not called "panics." It's an important distinction in compiled languages.
Thanks to point that out! I didn't pay attention to this distinction before!