Control Flow

Control flow statements are used to control the flow of execution in a program. They are used to make decisions, to repeat a block of code, and to exit a block of code early. Move has the following control flow statements (explained in detail below):

Conditional Statements

The if expression is used to make decisions in a program. It evaluates a boolean expression and executes a block of code if the expression is true. Paired with else, it can execute a different block of code if the expression is false.

The syntax for the if expression is:

if (<bool_expression>) <expression>;
if (<bool_expression>) <expression> else <expression>;

Just like any other expression, if requires a semicolon, if there are other expressions following it. The else keyword is optional, except for the case when the resulting value is assigned to a variable. We will cover this below.

#[test]
fun test_if() {
    let x = 5;

    // `x > 0` is a boolean expression.
    if (x > 0) {
        std::debug::print(&b"X is bigger than 0".to_string())
    };
}

Let's see how we can use if and else to assign a value to a variable:

#[test]
fun test_if_else() {
    let x = 5;
    let y = if (x > 0) {
        1
    } else {
        0
    };

    assert!(y == 1);
}

Here we assign the value of the if expression to the variable y. If x is greater than 0, y will be assigned the value 1, otherwise 0. The else block is necessary, because both branches must return a value of the same type. If we omit the else block, the compiler will throw an error.

Conditional expressions are one of the most important control flow statements in Move. They can use either user provided input or some already stored data to make decisions. In particular, they are used in the assert! macro to check if a condition is true, and if not, to abort execution. We will get to it very soon!

Repeating Statements with Loops

Loops are used to execute a block of code multiple times. Move has two built-in types of loops: loop and while. In many cases they can be used interchangeably, but usually while is used when the number of iterations is known in advance, and loop is used when the number of iterations is not known in advance or there are multiple exit points.

Loops are helpful when dealing with collections, such as vectors, or when we want to repeat a block of code until a certain condition is met. However, it is important to be careful with loops, as they can lead to infinite loops, which can lead to gas exhaustion and the transaction being aborted.

The while loop

The while statement is used to execute a block of code as long as a boolean expression is true. Just like we've seen with if, the boolean expression is evaluated before each iteration of the loop. Just like conditional statements, the while loop is an expression and requires a semicolon if there are other expressions following it.

The syntax for the while loop is:

while (<bool_expression>) { <expressions>; };

Here is an example of a while loop with a very simple condition:

// This function iterates over the `x` variable until it reaches 10, the
// return value is the number of iterations it took to reach 10.
//
// If `x` is 0, then the function will return 10.
// If `x` is 5, then the function will return 5.
fun while_loop(mut x: u8): u8 {
    let mut y = 0;

    // This will loop until `x` is 10.
    // And will never run if `x` is 10 or more.
    while (x < 10) {
        y = y + 1;
        x = x + 1;
    };

    y
}

#[test]
fun test_while() {
    assert!(while_loop(0) == 10); // 10 times
    assert!(while_loop(5) == 5);  // 5 times
    assert!(while_loop(10) == 0); // loop never executed
}

Infinite loop

Now let's imagine a scenario where the boolean expression is always true. For example, if we literally passed true to the while condition. As you might expect, this would create an infinite loop, and this is almost what the loop statement works like.

#[test, expected_failure(out_of_gas, location=Self)]
fun test_infinite_while() {
    let mut x = 0;

    // This will loop forever.
    while (true) {
        x = x + 1;
    };

    // This line will never be executed.
    assert!(x == 5);
}

An infinite while, or while without a condition, is a loop. The syntax for it is simple:

loop { <expressions>; };

Let's rewrite the previous example using loop instead of while:

#[test, expected_failure(out_of_gas, location=Self)]
fun test_infinite_loop() {
    let mut x = 0;

    // This will loop forever.
    loop {
        x = x + 1;
    };

    // This line will never be executed.
    assert!(x == 5);
}

Infinite loops on their own are not very useful in Move, since every operation in Move costs gas, and an infinite loop will lead to gas exhaustion. However, they can be used in combination with break and continue statements to create more complex loops.

Exiting a Loop Early

As we already mentioned, infinite loops are rather useless on their own. And that's where we introduce the break and continue statements. They are used to exit a loop early, and to skip the rest of the current iteration, respectively.

Syntax for the break statement is (without a semicolon):

break

The break statement is used to stop the execution of a loop and exit it early. It is often used in combination with a conditional statement to exit the loop when a certain condition is met. To illustrate this point, let's turn the infinite loop from the previous example into something that looks and behaves more like a while loop:

#[test]
fun test_break_loop() {
    let mut x = 0;

    // This will loop until `x` is 5.
    loop {
        x = x + 1;

        // If `x` is 5, then exit the loop.
        if (x == 5) {
            break // Exit the loop.
        }
    };

    assert!(x == 5);
}

Almost identical to the while loop, right? The break statement is used to exit the loop when x is 5. If we remove the break statement, the loop will run forever, just like the previous example.

Skipping an Iteration

The continue statement is used to skip the rest of the current iteration and start the next one. Similarly to break, it is used in combination with a conditional statement to skip the rest of the iteration when a certain condition is met.

Syntax for the continue statement is (without a semicolon):

continue

The example below skips odd numbers and prints only even numbers from 0 to 10:

#[test]
fun test_continue_loop() {
    let mut x = 0;

    // This will loop until `x` is 10.
    loop {
        x = x + 1;

        // If `x` is odd, then skip the rest of the iteration.
        if (x % 2 == 1) {
            continue // Skip the rest of the iteration.
        };

        std::debug::print(&x);

        // If `x` is 10, then exit the loop.
        if (x == 10) {
            break // Exit the loop.
        }
    };

    assert!(x == 10); // 10
}

break and continue statements can be used in both while and loop loops.

Early Return

The return statement is used to exit a function early and return a value. It is often used in combination with a conditional statement to exit the function when a certain condition is met. The syntax for the return statement is:

return <expression>

Here is an example of a function that returns a value when a certain condition is met:

/// This function returns `true` if `x` is greater than 0 and not 5,
/// otherwise it returns `false`.
fun is_positive(x: u8): bool {
    if (x == 5) {
        return false
    };

    if (x > 0) {
        return true
    };

    false
}

#[test]
fun test_return() {
    assert!(is_positive(5) == false);
    assert!(is_positive(0) == false);
    assert!(is_positive(1) == true);
}

Unlike in other languages, the return statement is not required for the last expression in a function. The last expression in a function block is automatically returned. However, the return statement is useful when we want to exit a function early if a certain condition is met.