RustLoops (loop, while, for)

Loops in Rust

Rust has three looping constructs: loop for infinite loops with explicit breaks, while for condition-driven loops, and for for iterating over ranges and collections. Each has a distinct purpose, and Rust's iterator system makes for loops exceptionally powerful.

The loop Keyword

loop creates an infinite loop. It runs the block repeatedly until a break statement is hit. It is the right tool when you do not know in advance how many iterations you need — for example, retrying an operation until it succeeds.

RUST
fn main() {
    let mut count = 0;

    loop {
        count += 1;
        println!("count = {}", count);

        if count == 3 {
            break; // exit the loop
        }
    }

    println!("Done! count = {}", count);
}
count = 1
count = 2
count = 3
Done! count = 3
loop as an Expression (Breaking with a Value)

Like if, loop in Rust is an expression — you can return a value from it by putting the value after break. This is unique to Rust and extremely useful for retry logic where you want to capture the result of the successful attempt.

RUST
fn main() {
    let mut attempt = 0;

    // The loop expression evaluates to the value passed to break
    let result = loop {
        attempt += 1;

        if attempt == 3 {
            break attempt * 10; // break with a value
        }
    };

    println!("Succeeded on attempt {}, result = {}", attempt, result);
    // Succeeded on attempt 3, result = 30
}

// Practical example: keep asking for input until valid
fn get_positive_number(input_sequence: &[i32]) -> i32 {
    let mut iter = input_sequence.iter();

    loop {
        let n = *iter.next().unwrap();
        if n > 0 {
            break n; // return this value from the loop
        }
        println!("{} is not positive, try again", n);
    }
}

fn demo() {
    let numbers = [-1, -3, 0, 5, 8];
    let valid = get_positive_number(&numbers);
    println!("Got valid number: {}", valid); // 5
}
Tip
The type of a `loop` expression is inferred from its `break` values. All `break` statements in the same loop must produce the same type.
Loop Labels for Nested Loops

When loops are nested, break and continue normally apply to the innermost loop. Loop labels let you target an outer loop explicitly. Labels start with a single quote (same syntax as Rust lifetimes).

RUST
fn main() {
    let mut found = false;

    // Label the outer loop
    'outer: for i in 0..5 {
        for j in 0..5 {
            if i + j == 6 {
                println!("Found: i={}, j={}", i, j);
                found = true;
                break 'outer; // break the OUTER loop, not just the inner one
            }
        }
    }

    println!("found = {}", found); // found = true
}

fn matrix_search() {
    let matrix = vec![
        vec![1, 2, 3],
        vec![4, 5, 6],
        vec![7, 8, 9],
    ];
    let target = 5;

    'search: for (row_idx, row) in matrix.iter().enumerate() {
        for (col_idx, &val) in row.iter().enumerate() {
            if val == target {
                println!("Found {} at row {}, col {}", target, row_idx, col_idx);
                break 'search;
            }
        }
    }
}
Note
Loop labels also work with `continue`: `continue 'outer` skips to the next iteration of the labeled outer loop instead of the inner one.
The while Loop

while repeats a block as long as a condition is true. It checks the condition before each iteration, so if the condition starts out false the body never runs.

RUST
fn main() {
    // Countdown example
    let mut countdown = 5;

    while countdown > 0 {
        println!("{}...", countdown);
        countdown -= 1;
    }
    println!("Liftoff!");
}

fn collatz(mut n: u64) -> u32 {
    let mut steps = 0;
    while n != 1 {
        if n % 2 == 0 {
            n /= 2;
        } else {
            n = 3 * n + 1;
        }
        steps += 1;
    }
    steps
}

fn demo_collatz() {
    println!("Collatz steps for 27: {}", collatz(27)); // 111
}
5...
4...
3...
2...
1...
Liftoff!
while let

while let is a convenient shorthand that keeps looping as long as a pattern matches. It is frequently used with Option values from iterators or channels.

RUST
fn main() {
    let mut stack = vec![1, 2, 3, 4, 5];

    // Keep popping while there are elements
    while let Some(top) = stack.pop() {
        println!("Popped: {}", top);
    }
    println!("Stack is empty");
    // Popped: 5, 4, 3, 2, 1

    // Equivalent using a regular while:
    // while !stack.is_empty() {
    //     let top = stack.pop().unwrap();
    //     ...
    // }
}
The for Loop with Ranges

for with a range is the most common loop for a known number of iterations. Use .. for exclusive ranges and ..= for inclusive ranges.

RUST
fn main() {
    // Exclusive range: 0, 1, 2, 3, 4
    for i in 0..5 {
        print!("{} ", i);
    }
    println!(); // 0 1 2 3 4

    // Inclusive range: 0, 1, 2, 3, 4, 5
    for i in 0..=5 {
        print!("{} ", i);
    }
    println!(); // 0 1 2 3 4 5

    // Sum 1 to 100
    let sum: i32 = (1..=100).sum();
    println!("Sum 1..=100 = {}", sum); // 5050

    // Reverse a range
    for i in (0..5).rev() {
        print!("{} ", i);
    }
    println!(); // 4 3 2 1 0
}
for with Iterators

for works with any type that implements the IntoIterator trait. Rust collections — Vec, arrays, HashMap, etc. — all implement it.

RUST
fn main() {
    let fruits = vec!["apple", "banana", "cherry"];

    // .iter() — borrows each element (&T), collection stays usable after
    for fruit in fruits.iter() {
        println!("Fruit: {}", fruit);
    }

    // Shorthand for .iter() — same thing
    for fruit in &fruits {
        println!("{}", fruit);
    }

    // .into_iter() — takes ownership, collection is consumed
    let numbers = vec![1, 2, 3];
    for n in numbers.into_iter() {
        println!("{}", n);
    }
    // numbers cannot be used here — it was moved

    // .iter_mut() — mutable references, allows modification in place
    let mut scores = vec![70, 85, 90];
    for score in scores.iter_mut() {
        *score += 5; // dereference to modify
    }
    println!("{:?}", scores); // [75, 90, 95]
}
Tip
Use `.iter()` (or `&collection`) when you want to read values without consuming the collection. Use `.into_iter()` when you want to consume the collection. Use `.iter_mut()` when you need to modify elements in place.
enumerate() — Index and Value Together

When you need both the index and the value, call .enumerate() on the iterator. It wraps each element in a (index, value) tuple.

RUST
fn main() {
    let languages = vec!["Rust", "Go", "Python", "JavaScript"];

    for (index, language) in languages.iter().enumerate() {
        println!("{}. {}", index + 1, language);
    }
    // 1. Rust
    // 2. Go
    // 3. Python
    // 4. JavaScript

    // Find the position of an element
    let target = "Python";
    for (i, &lang) in languages.iter().enumerate() {
        if lang == target {
            println!("{} is at index {}", target, i);
        }
    }
}
zip() — Iterating Two Collections Together

.zip() pairs up two iterators element-by-element, stopping at the shorter one. It is the idiomatic way to iterate over two collections simultaneously.

RUST
fn main() {
    let names  = vec!["Alice", "Bob", "Carol"];
    let scores = vec![95, 87, 92];

    for (name, score) in names.iter().zip(scores.iter()) {
        println!("{}: {}", name, score);
    }
    // Alice: 95
    // Bob: 87
    // Carol: 92

    // Zip two ranges
    let pairs: Vec<(i32, i32)> = (1..=3).zip(4..=6).collect();
    println!("{:?}", pairs); // [(1, 4), (2, 5), (3, 6)]
}
step_by() — Custom Step Size

By default, ranges increment by 1. Use .step_by(n) to increment by a different amount. This is especially useful for working with even/odd numbers or skipping values.

RUST
fn main() {
    // Even numbers from 0 to 10
    for i in (0..=10).step_by(2) {
        print!("{} ", i);
    }
    println!(); // 0 2 4 6 8 10

    // Count by 5
    for i in (0..=25).step_by(5) {
        print!("{} ", i);
    }
    println!(); // 0 5 10 15 20 25
}
break and continue

break exits the loop immediately. continue skips the rest of the current iteration and jumps to the next one. Both work in loop, while, and for.

RUST
fn main() {
    // continue — skip even numbers
    print!("Odd numbers: ");
    for i in 0..10 {
        if i % 2 == 0 {
            continue; // skip even
        }
        print!("{} ", i);
    }
    println!(); // 1 3 5 7 9

    // break — stop at first number divisible by 7
    print!("Searching: ");
    for i in 1..100 {
        print!("{} ", i);
        if i % 7 == 0 {
            println!();
            println!("Found first multiple of 7: {}", i);
            break;
        }
    }
}
Real Examples

Putting it all together with practical use cases.

RUST
fn sum_vector(numbers: &[i32]) -> i32 {
    let mut total = 0;
    for n in numbers {
        total += n;
    }
    total
    // Idiomatic alternative: numbers.iter().sum()
}

fn find_first(haystack: &[i32], needle: i32) -> Option<usize> {
    for (i, &val) in haystack.iter().enumerate() {
        if val == needle {
            return Some(i);
        }
    }
    None
}

fn is_prime(n: u64) -> bool {
    if n < 2 { return false; }
    if n == 2 { return true; }
    if n % 2 == 0 { return false; }

    let mut i = 3u64;
    while i * i <= n {
        if n % i == 0 { return false; }
        i += 2;
    }
    true
}

fn main() {
    let nums = vec![3, 1, 4, 1, 5, 9, 2, 6];
    println!("Sum: {}", sum_vector(&nums));             // Sum: 31
    println!("Index of 9: {:?}", find_first(&nums, 9)); // Index of 9: Some(5)

    let primes: Vec<u64> = (2..30).filter(|&n| is_prime(n)).collect();
    println!("Primes under 30: {:?}", primes);
    // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
}
Loop Types at a Glance

Loop type

Use when...

Can return value?

Iteration control

loop

Repeat indefinitely until a condition inside triggers break

Yes — break with value

break, continue, labels

while

Repeat while an upfront condition holds

No

break, continue, labels

while let

Repeat while a pattern continues to match

No

break, continue, labels

for .. in ..

Iterate over a range or collection

No

break, continue, labels

Success
You now know all three of Rust's loop constructs. Key takeaways: loop is for infinite loops and can return a value via break, while is for condition-driven iteration, and for with iterators is the idiomatic way to traverse collections. Use enumerate() for index+value pairs, zip() for two collections at once, and step_by() for non-unit increments.