DEV Community

Cover image for Learning Rust ๐Ÿฆ€: 04- The basics: Flow Control
Fady GA ๐Ÿ˜Ž
Fady GA ๐Ÿ˜Ž

Posted on • Edited on

Learning Rust ๐Ÿฆ€: 04- The basics: Flow Control

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

}
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿฆ€ 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}")
}

Enter fullscreen mode Exit fullscreen mode

This will print the following in stdout:

Effective value is 0
Enter fullscreen mode Exit fullscreen mode

(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"
    };
Enter fullscreen mode Exit fullscreen mode

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 {}
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

The output of the previous code will be:

Enter loop
Outside the loop
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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}")
}
Enter fullscreen mode Exit fullscreen mode

This code will output:

The class capacity is 30
Enter fullscreen mode Exit fullscreen mode

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!")
}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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 each count_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!")
}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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}")
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ We could have just obtained the same result using while and an index 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!")
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
webbureaucrat profile image
webbureaucrat

๐Ÿฆ€ New Rust Terminology: "panic" means that the program has errored out!

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.

Collapse
 
fadygrab profile image
Fady GA ๐Ÿ˜Ž

Thanks to point that out! I didn't pay attention to this distinction before!