Control Flow

for Loops

A for loop iterates over a sequence of elements produced by any type that implements the IntoIterator trait—including arrays, slices, vectors, ranges, strings, and custom iterators—executing its block once for each item.

Basic Syntax and Execution

A for loop has the form for PATTERN in ITERABLE_EXPRESSION { ... }:

fn main() {
    let primes = [2, 3, 5, 7, 11];

    for number in primes {
        println!("Prime: {number}");
    }
}
Prime: 2
Prime: 3
Prime: 5
Prime: 7
Prime: 11

Unlike C-style index loops (for (int i = 0; i < len; i++)), Rust's for loop advances through the sequence automatically. Because it never computes an out-of-bounds index or forgets to increment a counter, it eliminates off-by-one bugs and allows the compiler to elide per-element bounds checks.

How for Desugars Under the Hood

When rustc compiles for item in collection { body }, it lowers the loop into a call to IntoIterator::into_iter followed by a loop that repeatedly calls .next() on the resulting iterator:

Surface Syntax:                         Compiler Desugaring (Conceptual):
for item in iterable {                  {
    body                                    let mut iter = IntoIterator::into_iter(iterable);
}                                           loop {
                                                match iter.next() {
                                                    Some(item) => { body }
                                                    None => break,
                                                }
                                            }
                                        }

Because iter.next() returns Option<T>, the loop runs for every Some(item) value and exits cleanly as soon as the iterator returns None.

The Three Iteration Modes: Move, Shared Borrow, and Mutable Borrow

How you write the iterable expression after in controls whether the for loop consumes the collection, borrows its elements immutably, or borrows its elements mutably:

Loop Header Equivalent Method Call Type of item Collection Usable After Loop?
for item in collection collection.into_iter() T (owned value) No (unless the collection itself is Copy, like [i32; 3])
for item in &collection collection.iter() &T (shared reference) Yes (collection is only borrowed immutably)
for item in &mut collection collection.iter_mut() &mut T (mutable reference) Yes (elements can be modified in place)

1. Borrowing Immutably (for item in &collection)

Borrowing the collection with & lets you inspect every element while keeping the collection intact for later use:

fn main() {
    let names = vec![String::from("Alice"), String::from("Bob")];

    for name in &names {
        // `name` has type `&String`
        println!("Hello, {name}!");
    }

    // `names` is still owned and valid here
    println!("Total names: {}", names.len());
}
Hello, Alice!
Hello, Bob!
Total names: 2

2. Borrowing Mutably (for item in &mut collection)

Borrowing with &mut yields a mutable reference (&mut T) to each element, allowing you to modify the collection in place using the dereference operator *:

fn main() {
    let mut scores = [70, 85, 90];

    for score in &mut scores {
        // `score` has type `&mut i32`
        *score += 5;
    }

    println!("Curved scores: {scores:?}");
}
Curved scores: [75, 90, 95]

3. Consuming By Value (for item in collection)

Passing a non-Copy collection directly without & moves ownership of the collection into the loop and transfers ownership of each element T into the loop body:

fn main() {
    let messages = vec![String::from("first"), String::from("second")];

    for msg in messages {
        // `msg` is an owned `String` that will be dropped at the end of this iteration
        println!("Consumed: {msg}");
    }

    // `messages` has been moved and cannot be used here!
}
Consumed: first
Consumed: second

Destructuring Patterns in the for Header

Because the binding position after for accepts any irrefutable pattern, you can destructure tuples, structs, or references directly in the loop header.

A common pattern combines .iter().enumerate()—which yields (usize, &T) pairs—with tuple destructuring for (index, value) in ...:

fn main() {
    let tasks = ["Compile", "Test", "Deploy"];

    for (index, task) in tasks.iter().enumerate() {
        println!("Step {}: {task}", index + 1);
    }

    let points = [(0, 0), (3, 4), (6, 8)];
    for &(x, y) in &points {
        println!("Point at x={x}, y={y}");
    }
}
Step 1: Compile
Step 2: Test
Step 3: Deploy
Point at x=0, y=0
Point at x=3, y=4
Point at x=6, y=8

Using break and continue in for Loops

Inside a for loop:

  • continue skips the rest of the current iteration and advances the iterator to the next element.
  • break stops iteration immediately and exits the for loop.

Like while, a for loop always evaluates to () and cannot return a value with break value; (because the iterable might be empty and execute zero iterations).

fn main() {
    let numbers = [4, -2, 7, 0, 9, -5];
    let mut sum_positive = 0;

    for n in numbers {
        if n < 0 {
            continue; // Skip negative numbers
        }
        if n == 0 {
            break; // Stop completely when 0 is encountered
        }
        sum_positive += n;
    }

    println!("Sum before zero: {sum_positive}");
}
Sum before zero: 11

Common Errors

Using a Collection After a By-Value for Loop Consumes It (E0382)

Iterating over a Vec<T> or other non-Copy collection without & moves the collection into IntoIterator::into_iter:

// ❌ does not compile
fn main() {
    let items = vec![String::from("a"), String::from("b")];

    for item in items {
        println!("{item}");
    }

    println!("Count: {}", items.len());
}
error[E0382]: borrow of moved value: `items`
 --> src/main.rs:8:27
  |
2 |     let items = vec![String::from("a"), String::from("b")];
  |         ----- move occurs because `items` has type `Vec<String>`, which does not implement the `Copy` trait
3 |
4 |     for item in items {
  |                 ----- `items` moved due to this implicit call to `.into_iter()`
...
8 |     println!("Count: {}", items.len());
  |                           ^^^^^ value borrowed here after move
  |
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
  |
4 |     for item in &items {
  |                 +

error: aborting due to 1 previous error