Control Flow

Control flow constructs allow you to run code conditionally or repeatedly, directing the "flow" of your program's execution. Rust provides several familiar ways to do this.

Execute Code based on a Condition Using if and else

Rust by example - if else

if, else, and else if allow you to execute different blocks of code based on whether a boolean condition is true or false:

fn main() {
    let number = 3;
    // The condition must be a boolean.
    if number > 0 {
        // If a condition operand evaluates to true,
        // the consequent block is executed and any subsequent else if or else
        // block is skipped.
        println!("Positive number");
    } else if number < 0 {
        println!("Negative number");
    } else {
        println!("Zero");
    }

    // Note that `if` is an expression, meaning it evaluates to a value.
    let result: u8 = if number < 5 {
        println!("Condition was true");
        5
    } else {
        println!("Condition was false");
        // The value of the `if` expression is the value of the last expression
        // in the executed block.
        6
    }; // Semicolon here, because `let` is a statement.
    println!("{result}");
}
// All branches of an `if/else` expression must evaluate to the same type.

Refer to the match chapter for if let expressions.

Create Loops Using loop

Rust by example - loop

loop creates an infinite loop that runs until explicitly stopped, usually with the break keyword. Like if, loop is an expression, and break can return a value from the loop.

/// Demonstrates the use of a `loop` with a `break` statement to return a value.
fn main() {
    // Initialize a mutable counter.
    let mut counter = 0;

    // The `loop` keyword creates an infinite loop.
    // Note that it is an expression.
    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
            // The value passed to `break` is returned by the loop.
            // `continue` and loop labels also exist.
            // See <https://doc.rust-lang.org/book/ch03-05-control-flow.html>
        }
    };
    println!("{result}");
}

You can also use continue to skip the rest of the current iteration and start the next one. Loops can also have labels ('label: loop { ... break 'label; }) for breaking or continuing to outer loops from within nested loops.

Execute Code Repeatedly While a Condition is True with while

Rust by example - while

while executes a block of code repeatedly as long as a boolean condition remains true. The condition is checked before each iteration.

/// This is an example of a `while` loop.
fn main() {
    // Initialize a mutable variable.
    let mut number = 5;
    // The loop continues as long as the condition is true.
    while number != 0 {
        print!("{number} ");
        // Decrement the number.
        number -= 1;
    }
    // Prints: 5 4 3 2 1
}

while supports break expressions and loop labels. Refer to the match chapter for while let loops.

Iterate Through a Collection with for

Rust by example - for

for is used to iterate over the items produced by an iterator.

Many collection types (like ranges, arrays, vectors, strings) implement the IntoIterator trait, which provides a method into_iter() that returns an iterator. They may also provide additional methods such as iter() or iter_mut().

//! Examples using `for`.

fn main() {
    // Use a range to generate all numbers in sequence,
    // starting from one number and ending before another number.
    for number in 1..4 {
        println!("{number} ");
    }
    // a..=b can be used for a range that is inclusive on both ends.
    // You can also use `rev` for reverse enumeration.
    for number in (1..=3).rev() {
        println!("{number} ");
    }

    // Iterate over a collection, here an array.
    let a = [10, 20, 30];
    for element in a {
        println!("the value is: {element}");
    }

    // Pay attention to whether you're getting a reference (&T or &mut T)
    // or taking ownership (T) of the elements during iteration.
    // This impacts whether you can use the collection after the loop and
    // whether you can modify elements.
    //
    // By default, `for` will use the collection's implementation of
    // `std::iter::IntoIterator`, and call its `into_iter` function,
    // consuming the collection:
    let b = vec![42, 43];
    for element in b {
        // Implicit call to `into_iter()`.
        println!("the value is: {element}");
    }
    // ERROR: value borrowed here after move: println!("b: {:?}", b);

    // You can instead iterate by immutable reference with `iter()`.
    // This is the most common way to iterate over a collection,
    // when you just want to read its elements without modifying them.
    let mut c = vec![10, 20, 30];
    for num in c.iter() {
        println!("Element: {num}"); // `num` is an immutable reference.
    }

    // If you need to modify the elements of a collection during iteration, you
    // use `iter_mut()`, which provides a mutable reference to each element.
    for num in c.iter_mut() {
        *num *= 2; // Dereference the mutable reference to modify the value
    }
    println!("Modified numbers: {c:?}");

    // Strings in Rust are UTF-8.
    let s = "Hello👋";
    // They can be iterated over Unicode scalar values (characters):
    for c in s.chars() {
        println!("Character: {c}");
    }
    // Or over the raw UTF-8 bytes of the string:
    for b in s.bytes() {
        println!("Byte: {b}");
    }

    // You can use `enumerate` to produce both the index and the value
    // while iterating.
    let fruits = ["apple", "banana", "cherry"];
    for (index, fruit) in fruits.iter().enumerate() {
        println!("Fruit at index {index}: {fruit}");
    }
    // Note that (index, value) above is a pattern that destructures elements as
    // you iterate.
}

for loops support break expressions and loop labels.

Exit Early from Blocks

Labeled block expressions are like block expressions, except that they allow using break expressions within the block. You may think of them as a loop that executes only once and allows exiting early.

fn main() {
    // Labeled block expressions must begin with a label, here `'block`.
    let result = 'block: {
        println!("Entering the labeled block.");
        if condition() {
            println!("About to break with value 1");
            // Break expressions within a labeled block expression must have a
            // label.
            break 'block 1;
        }
        if !condition() {
            println!("About to break with value 2");
            break 'block 2;
        }
        println!("About to return with value 3");
        3
    };
    println!("Result: {result}");
}

fn condition() -> bool {
    false
}

References

  • Match.
  • Rust Patterns.