Rust - Loops

Overview

Estimated time: 45–55 minutes

Master all loop types in Rust: infinite loops with loop, conditional loops with while, and iteration with for. Learn about loop control with break and continue, loop labels, and common iteration patterns.

Learning Objectives

Prerequisites

The Loop Statement

The loop keyword creates an infinite loop that runs until explicitly broken.

Basic Infinite Loop

fn main() {
    let mut count = 0;
    
    loop {
        count += 1;
        println!("Count: {}", count);
        
        if count >= 5 {
            break;  // Exit the loop
        }
    }
    
    println!("Loop finished!");
}

Expected Output:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Loop finished!

Returning Values from Loops

Loops are expressions and can return values when broken:

fn main() {
    let mut counter = 0;
    
    let result = loop {
        counter += 1;
        
        if counter == 10 {
            break counter * 2;  // Return a value when breaking
        }
    };
    
    println!("The result is: {}", result);
    
    // Another example: finding first even number > 10
    let mut num = 11;
    let first_even = loop {
        if num % 2 == 0 {
            break num;
        }
        num += 1;
    };
    
    println!("First even number > 10: {}", first_even);
}

Expected Output:

The result is: 20
First even number > 10: 12

While Loops

While loops continue executing as long as a condition is true.

Basic While Loop

fn main() {
    let mut number = 3;
    
    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }
    
    println!("LIFTOFF!!!");
    
    // Another example: calculating sum
    let mut sum = 0;
    let mut i = 1;
    
    while i <= 10 {
        sum += i;
        i += 1;
    }
    
    println!("Sum of 1 to 10: {}", sum);
}

Expected Output:

3!
2!
1!
LIFTOFF!!!
Sum of 1 to 10: 55

While Loop with Complex Conditions

fn main() {
    let mut x = 1;
    let mut y = 1;
    
    // Fibonacci sequence while sum < 100
    while x + y < 100 {
        let temp = x + y;
        x = y;
        y = temp;
        println!("Fibonacci: {}", x);
    }
    
    // Processing input until condition met
    let mut attempts = 0;
    let mut success = false;
    
    while !success && attempts < 5 {
        attempts += 1;
        
        // Simulate some operation
        let random_success = attempts > 3; // Simulate success after 3 attempts
        
        if random_success {
            success = true;
            println!("Success on attempt {}!", attempts);
        } else {
            println!("Attempt {} failed", attempts);
        }
    }
    
    if !success {
        println!("Failed after {} attempts", attempts);
    }
}

Expected Output:

Fibonacci: 1
Fibonacci: 2
Fibonacci: 3
Fibonacci: 5
Fibonacci: 8
Fibonacci: 13
Fibonacci: 21
Fibonacci: 34
Fibonacci: 55
Fibonacci: 89
Attempt 1 failed
Attempt 2 failed
Attempt 3 failed
Success on attempt 4!

For Loops

For loops are used to iterate over collections and ranges. They're the most common type of loop in Rust.

Iterating Over Ranges

fn main() {
    // Exclusive range (1 to 4)
    println!("Counting from 1 to 5 (exclusive):");
    for number in 1..5 {
        println!("{}", number);
    }
    
    // Inclusive range (1 to 5)
    println!("\nCounting from 1 to 5 (inclusive):");
    for number in 1..=5 {
        println!("{}", number);
    }
    
    // Reverse range
    println!("\nCountdown:");
    for number in (1..=5).rev() {
        println!("{}", number);
    }
    println!("Blast off!");
}

Expected Output:

Counting from 1 to 5 (exclusive):
1
2
3
4

Counting from 1 to 5 (inclusive):
1
2
3
4
5

Countdown:
5
4
3
2
1
Blast off!

Iterating Over Arrays and Vectors

fn main() {
    let arr = [10, 20, 30, 40, 50];
    
    // Iterate over array elements
    println!("Array elements:");
    for element in arr {
        println!("{}", element);
    }
    
    // Iterate with index using enumerate
    println!("\nArray with indices:");
    for (index, value) in arr.iter().enumerate() {
        println!("Index {}: {}", index, value);
    }
    
    // Vector iteration
    let vec = vec!["apple", "banana", "cherry"];
    
    println!("\nVector elements:");
    for fruit in &vec {  // Borrow each element
        println!("{}", fruit);
    }
    
    // Mutable iteration
    let mut numbers = vec![1, 2, 3, 4, 5];
    
    println!("\nDoubling numbers:");
    for num in &mut numbers {  // Mutable borrow
        *num *= 2;
        println!("{}", num);
    }
    
    println!("Final vector: {:?}", numbers);
}

Expected Output:

Array elements:
10
20
30
40
50

Array with indices:
Index 0: 10
Index 1: 20
Index 2: 30
Index 3: 40
Index 4: 50

Vector elements:
apple
banana
cherry

Doubling numbers:
2
4
6
8
10
Final vector: [2, 4, 6, 8, 10]

Iterating Over Characters and Strings

fn main() {
    let text = "Hello, Rust!";
    
    // Iterate over characters
    println!("Characters:");
    for ch in text.chars() {
        println!("{}", ch);
    }
    
    // Iterate over bytes
    println!("\nBytes:");
    for byte in text.bytes() {
        println!("{}", byte);
    }
    
    // Iterate over words
    println!("\nWords:");
    for word in text.split_whitespace() {
        println!("{}", word);
    }
    
    // Count vowels example
    let mut vowel_count = 0;
    for ch in text.to_lowercase().chars() {
        match ch {
            'a' | 'e' | 'i' | 'o' | 'u' => vowel_count += 1,
            _ => {}
        }
    }
    
    println!("\nVowel count in '{}': {}", text, vowel_count);
}

Expected Output:

Characters:
H
e
l
l
o
,
 
R
u
s
t
!

Bytes:
72
101
108
108
111
44
32
82
117
115
116
33

Words:
Hello,
Rust!

Vowel count in 'Hello, Rust!': 3

Loop Control: Break and Continue

Using Break

break exits the loop immediately:

fn main() {
    // Break out of infinite loop
    let mut counter = 0;
    loop {
        counter += 1;
        
        if counter == 3 {
            println!("Breaking at counter = {}", counter);
            break;
        }
        
        println!("Counter: {}", counter);
    }
    
    // Break with return value
    let result = loop {
        counter += 1;
        
        if counter > 10 {
            break counter - 10;
        }
    };
    
    println!("Result: {}", result);
    
    // Break in for loop
    println!("\nSearching for number 7:");
    for num in 1..=10 {
        if num == 7 {
            println!("Found 7!");
            break;
        }
        println!("Checking {}", num);
    }
}

Expected Output:

Counter: 1
Counter: 2
Breaking at counter = 3
Result: 1

Searching for number 7:
Checking 1
Checking 2
Checking 3
Checking 4
Checking 5
Checking 6
Found 7!

Using Continue

continue skips the rest of the current iteration:

fn main() {
    // Skip even numbers
    println!("Odd numbers from 1 to 10:");
    for num in 1..=10 {
        if num % 2 == 0 {
            continue;  // Skip even numbers
        }
        println!("{}", num);
    }
    
    // Processing with conditions
    let numbers = vec![1, -2, 3, -4, 5, -6];
    let mut positive_sum = 0;
    
    println!("\nProcessing numbers:");
    for num in numbers {
        if num < 0 {
            println!("Skipping negative number: {}", num);
            continue;
        }
        
        positive_sum += num;
        println!("Adding positive number: {}", num);
    }
    
    println!("Sum of positive numbers: {}", positive_sum);
}

Expected Output:

Odd numbers from 1 to 10:
1
3
5
7
9

Processing numbers:
Adding positive number: 1
Skipping negative number: -2
Adding positive number: 3
Skipping negative number: -4
Adding positive number: 5
Skipping negative number: -6
Sum of positive numbers: 9

Loop Labels

Loop labels allow you to break or continue outer loops from inner loops.

Nested Loops with Labels

fn main() {
    println!("Searching for coordinate (2, 3):");
    
    'outer: for x in 0..5 {
        for y in 0..5 {
            println!("Checking ({}, {})", x, y);
            
            if x == 2 && y == 3 {
                println!("Found target at ({}, {})!", x, y);
                break 'outer;  // Break out of the outer loop
            }
        }
    }
    
    println!("Search complete!");
    
    // Another example: continue outer loop
    println!("\nSkipping rows with even x:");
    'rows: for x in 1..=4 {
        if x % 2 == 0 {
            println!("Skipping row {}", x);
            continue 'rows;
        }
        
        for y in 1..=3 {
            println!("  ({}, {})", x, y);
        }
    }
}

Expected Output:

Searching for coordinate (2, 3):
Checking (0, 0)
Checking (0, 1)
Checking (0, 2)
Checking (0, 3)
Checking (0, 4)
Checking (1, 0)
Checking (1, 1)
Checking (1, 2)
Checking (1, 3)
Checking (1, 4)
Checking (2, 0)
Checking (2, 1)
Checking (2, 2)
Checking (2, 3)
Found target at (2, 3)!
Search complete!

Skipping rows with even x:
  (1, 1)
  (1, 2)
  (1, 3)
Skipping row 2
  (3, 1)
  (3, 2)
  (3, 3)

Complex Label Usage

fn main() {
    let matrix = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ];
    
    let target = 5;
    let mut found = false;
    
    'search: for (row_idx, row) in matrix.iter().enumerate() {
        for (col_idx, &value) in row.iter().enumerate() {
            println!("Checking matrix[{}][{}] = {}", row_idx, col_idx, value);
            
            if value == target {
                println!("Found {} at position ({}, {})", target, row_idx, col_idx);
                found = true;
                break 'search;
            }
        }
    }
    
    if !found {
        println!("Target {} not found in matrix", target);
    }
}

Expected Output:

Checking matrix[0][0] = 1
Checking matrix[0][1] = 2
Checking matrix[0][2] = 3
Checking matrix[1][0] = 4
Checking matrix[1][1] = 5
Found 5 at position (1, 1)

Iterator Methods and Advanced Patterns

Iterator Combinators

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Chain multiple operations
    let result: Vec = numbers
        .iter()
        .filter(|&&x| x % 2 == 0)    // Keep even numbers
        .map(|&x| x * x)             // Square them
        .collect();                  // Collect into vector
    
    println!("Even numbers squared: {:?}", result);
    
    // Find first element matching condition
    let first_big = numbers
        .iter()
        .find(|&&x| x > 5);
    
    match first_big {
        Some(value) => println!("First number > 5: {}", value),
        None => println!("No number > 5 found"),
    }
    
    // Enumerate with iterator
    println!("\nNumbers with positions:");
    for (pos, value) in numbers.iter().enumerate() {
        if pos % 2 == 0 {  // Only even positions
            println!("Position {}: {}", pos, value);
        }
    }
}

Expected Output:

Even numbers squared: [4, 16, 36, 64, 100]
First number > 5: 6

Numbers with positions:
Position 0: 1
Position 2: 3
Position 4: 5
Position 6: 7
Position 8: 9

Custom Iterator Patterns

fn main() {
    // Range with step
    println!("Every 3rd number from 0 to 20:");
    for i in (0..=20).step_by(3) {
        println!("{}", i);
    }
    
    // Zip two iterators
    let names = vec!["Alice", "Bob", "Charlie"];
    let ages = vec![25, 30, 35];
    
    println!("\nPeople:");
    for (name, age) in names.iter().zip(ages.iter()) {
        println!("{} is {} years old", name, age);
    }
    
    // Take and skip
    let numbers: Vec = (1..=10).collect();
    
    println!("\nFirst 5 numbers:");
    for num in numbers.iter().take(5) {
        println!("{}", num);
    }
    
    println!("\nLast 5 numbers (skip first 5):");
    for num in numbers.iter().skip(5) {
        println!("{}", num);
    }
}

Expected Output:

Every 3rd number from 0 to 20:
0
3
6
9
12
15
18

People:
Alice is 25 years old
Bob is 30 years old
Charlie is 35 years old

First 5 numbers:
1
2
3
4
5

Last 5 numbers (skip first 5):
6
7
8
9
10

Practical Examples

Input Processing Loop

fn main() {
    // Simulate processing user input
    let inputs = vec!["5", "10", "invalid", "15", "quit"];
    let mut sum = 0;
    let mut count = 0;
    
    for input in inputs {
        if input == "quit" {
            println!("Quitting...");
            break;
        }
        
        match input.parse::() {
            Ok(num) => {
                sum += num;
                count += 1;
                println!("Added {}, running sum: {}", num, sum);
            }
            Err(_) => {
                println!("Invalid input '{}', skipping", input);
                continue;
            }
        }
    }
    
    if count > 0 {
        println!("Average: {:.2}", sum as f64 / count as f64);
    }
}

Expected Output:

Added 5, running sum: 5
Added 10, running sum: 15
Invalid input 'invalid', skipping
Added 15, running sum: 30
Quitting...
Average: 10.00

Game Loop Pattern

fn main() {
    let mut player_health = 100;
    let mut enemy_health = 80;
    let mut turn = 1;
    
    println!("Battle begins!");
    
    loop {
        println!("\n--- Turn {} ---", turn);
        println!("Player health: {}, Enemy health: {}", player_health, enemy_health);
        
        // Player attacks (simulate with simple calculation)
        let player_damage = 15 + (turn % 3) * 5;  // Variable damage
        enemy_health -= player_damage;
        println!("Player deals {} damage!", player_damage);
        
        if enemy_health <= 0 {
            println!("Enemy defeated! Player wins!");
            break;
        }
        
        // Enemy attacks
        let enemy_damage = 12 + (turn % 2) * 3;   // Variable damage
        player_health -= enemy_damage;
        println!("Enemy deals {} damage!", enemy_damage);
        
        if player_health <= 0 {
            println!("Player defeated! Enemy wins!");
            break;
        }
        
        turn += 1;
        
        // Safety check to prevent infinite loops
        if turn > 10 {
            println!("Battle takes too long, ending in a draw!");
            break;
        }
    }
    
    println!("Battle ended after {} turns", turn);
}

Expected Output:

Battle begins!

--- Turn 1 ---
Player health: 100, Enemy health: 80
Player deals 20 damage!
Enemy deals 15 damage!

--- Turn 2 ---
Player health: 85, Enemy health: 60
Player deals 25 damage!
Enemy deals 12 damage!

--- Turn 3 ---
Player health: 73, Enemy health: 35
Player deals 15 damage!
Enemy deals 15 damage!

--- Turn 4 ---
Player health: 58, Enemy health: 20
Player deals 20 damage!
Enemy defeated! Player wins!
Battle ended after 4 turns

Performance Considerations

Iterator Efficiency

fn main() {
    let large_vec: Vec = (1..=1000).collect();
    
    // Efficient: iterator adaptors are lazy
    let sum: i32 = large_vec
        .iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * 2)
        .sum();
    
    println!("Sum of doubled even numbers: {}", sum);
    
    // Less efficient: collecting intermediate results
    let even_numbers: Vec = large_vec
        .iter()
        .filter(|&&x| x % 2 == 0)
        .cloned()
        .collect();
    
    let doubled: Vec = even_numbers
        .iter()
        .map(|&x| x * 2)
        .collect();
    
    let sum2: i32 = doubled.iter().sum();
    println!("Same result, less efficient: {}", sum2);
}

Expected Output:

Sum of doubled even numbers: 500500
Same result, less efficient: 500500

Common Loop Patterns

Loop Until Success

fn main() {
    let mut attempts = 0;
    let success_threshold = 3;
    
    let result = loop {
        attempts += 1;
        println!("Attempt {}", attempts);
        
        // Simulate operation that might fail
        if attempts >= success_threshold {
            break Ok(format!("Success after {} attempts", attempts));
        } else if attempts >= 5 {
            break Err("Failed after maximum attempts".to_string());
        }
        
        println!("Operation failed, retrying...");
    };
    
    match result {
        Ok(msg) => println!("{}", msg),
        Err(err) => println!("Error: {}", err),
    }
}

Expected Output:

Attempt 1
Operation failed, retrying...
Attempt 2
Operation failed, retrying...
Attempt 3
Success after 3 attempts

Best Practices

Choosing the Right Loop Type

Performance Tips

Common Pitfalls

Checks for Understanding

  1. What's the difference between 1..5 and 1..=5?
  2. How do you return a value from a loop?
  3. When would you use continue instead of break?
  4. How do loop labels help with nested loops?
  5. What's the difference between for x in vec and for x in &vec?

Answers

  1. 1..5 excludes 5 (1,2,3,4), 1..=5 includes 5 (1,2,3,4,5)
  2. Use break value; where value is what you want to return
  3. continue skips the rest of current iteration; break exits the entire loop
  4. Labels allow you to break or continue outer loops from inner loops
  5. for x in vec takes ownership and moves each element; for x in &vec borrows each element

← PreviousNext →