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:
continueskips the rest of the current iteration and advances the iterator to the next element.breakstops iteration immediately and exits theforloop.
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