Module: Control Flow

Loops

Rust Programming: Control Flow - Loops

Rust provides several ways to execute code repeatedly, known as loops. These are essential for automating tasks and processing collections of data. Here's a breakdown of the different loop types in Rust:

1. loop - The Infinite Loop

The loop keyword creates an infinite loop. It continues executing indefinitely unless explicitly broken with a break statement.

fn main() {
    let mut counter = 0;

    loop {
        counter += 1;

        println!("Counter: {}", counter);

        if counter == 5 {
            break; // Exit the loop when counter reaches 5
        }
    }

    println!("Loop finished!");
}

Explanation:

  • loop { ... }: Defines the infinite loop.
  • counter += 1;: Increments the counter variable in each iteration.
  • if counter == 5 { break; }: This is the crucial part. The break statement exits the loop when counter equals 5. Without break, the loop would run forever.
  • println!("Loop finished!");: This line executes after the loop has terminated.

break with a value:

You can also use break to return a value from a loop. This is particularly useful when the loop is nested within a function.

fn find_first_positive(numbers: &[i32]) -> Option<i32> {
    for &num in numbers {
        if num > 0 {
            return Some(num); // Break and return Some(num)
        }
    }
    None // Return None if no positive number is found
}

fn main() {
    let numbers = [-2, -1, 0, 1, 2, -3];
    let first_positive = find_first_positive(&numbers);

    match first_positive {
        Some(num) => println!("First positive number: {}", num),
        None => println!("No positive number found."),
    }
}

2. while - Conditional Loop

The while loop executes a block of code as long as a specified condition is true.

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("Number: {}", number);
        number -= 1;
    }

    println!("LIFTOFF!!!");
}

Explanation:

  • while number != 0 { ... }: The loop continues as long as number is not equal to 0.
  • number -= 1;: Decrements number in each iteration. This is essential to eventually make the condition number != 0 false, otherwise the loop will be infinite.

3. for - Iterating over Collections

The for loop is used to iterate over a sequence of items, such as arrays, vectors, ranges, and other iterable data structures.

a) Iterating over a Range:

fn main() {
    for number in 1..4 { // 1, 2, 3 (exclusive of 4)
        println!("Number: {}", number);
    }

    println!("Finished!");
}

Explanation:

  • for number in 1..4 { ... }: Iterates over the range of numbers from 1 (inclusive) to 4 (exclusive). The .. operator creates a range.
  • 1..=4 would include 4 in the iteration.

b) Iterating over a Vector:

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("The value is: {}", element);
    }
}

Explanation:

  • a.iter(): Creates an iterator over the elements of the array a. Iterators are a fundamental concept in Rust for working with collections.
  • for element in a.iter() { ... }: Iterates over each element in the iterator.

c) Iterating with Mutable Access:

If you need to modify the elements of a collection while iterating, use a.iter_mut().

fn main() {
    let mut a = [10, 20, 30, 40, 50];

    for element in a.iter_mut() {
        *element += 5; // Dereference the element to modify its value
    }

    println!("Modified array: {:?}", a); // Output: [15, 25, 35, 45, 55]
}

Explanation:

  • a.iter_mut(): Creates a mutable iterator over the elements of the array a.
  • *element += 5;: The * operator dereferences the element (which is a reference to an element in the array), allowing you to modify the actual value in the array.

d) Iterating with Ownership (Consuming the Collection):

If you want to take ownership of the elements during iteration, use a.into_iter(). This consumes the original collection.

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.into_iter() {
        println!("The value is: {}", element);
    }

    // a is no longer valid here because ownership was moved into the loop.
    // println!("{:?}", a); // This would cause a compile error.
}

4. Control Flow within Loops

You can use continue and break statements within loops to control their execution:

  • continue: Skips the rest of the current iteration and proceeds to the next iteration.
fn main() {
    for number in 1..10 {
        if number % 2 == 0 {
            continue; // Skip even numbers
        }
        println!("Odd number: {}", number);
    }
}
  • break: Exits the loop entirely. (As shown in the loop example above).

5. Loop Labels

You can label loops to control which loop break or continue affects when you have nested loops.

fn main() {
    'outer: for x in 1..=3 {
        for y in 1..=3 {
            println!("x: {}, y: {}", x, y);
            if x == 2 && y == 2 {
                break 'outer; // Break the outer loop
            }
        }
    }
    println!("Finished!");
}

Explanation:

  • 'outer:: This labels the outer loop.
  • break 'outer;: This breaks the loop labeled 'outer. Without the label, it would only break the inner loop.

Key Takeaways:

  • loop: Infinite loop, requires break to exit.
  • while: Conditional loop, executes as long as a condition is true.
  • for: Iterates over a sequence of items. Use iter(), iter_mut(), or into_iter() depending on your needs.
  • continue: Skips the current iteration.
  • break: Exits the loop.
  • Loop Labels: Useful for controlling break and continue in nested loops.

Understanding these loop types and control flow statements is crucial for writing effective and efficient Rust programs. Practice using them to become comfortable with their behavior.