Why this matters
Most loops in real programs walk over something: the numbers 1 to 100, every element of an array, every line of input. for expresses exactly that, without the manual counter bookkeeping that causes off-by-one bugs and out-of-bounds reads. It is by far the most common loop in Rust, and it is your first contact with iterators, the mechanism behind a large part of idiomatic Rust (module 11).
for over a range
fn main() {
for i in 1..4 {
println!("i = {i}");
}
for i in 1..=4 {
print!("{i} ");
}
println!();
}
i = 1
i = 2
i = 3
1 2 3 4
1..4is a range: 1, 2, 3. The end is excluded (half-open).1..=4includes the end: 1, 2, 3, 4.- The loop variable
iis a new variable for each pass, scoped to the body. You do not declare it withlet, and you do not increment it yourself.
Half-open ranges are the convention because they combine well with zero-based indexes: an array of length n has valid indexes 0..n, a range of length b - a is a..b, and splitting 0..n at m gives 0..m and m..n with nothing lost or doubled.
A range is a value like any other. It can be stored in a variable, and it has methods:
fn main() {
let r = 0..10;
println!("{:?} has {} elements, contains 5: {}", r, r.len(), r.contains(&5));
for i in (0..10).step_by(3) {
print!("{i} ");
}
println!();
for i in (1..=5).rev() {
print!("{i} ");
}
println!();
}
0..10 has 10 elements, contains 5: true
0 3 6 9
5 4 3 2 1
step_by(3) takes every third value; rev() goes backwards. The parentheses around the range matter: 1..=5.rev() would try to call rev on the number 5.
Iterating over an array
You could loop over indexes:
fn main() {
let temps = [12, 15, 9, 20, 17];
for i in 0..temps.len() {
println!("day {i}: {}", temps[i]);
}
}
That works, but idiomatic Rust loops over the elements directly:
fn main() {
let temps = [12, 15, 9, 20, 17];
let mut total = 0;
for t in temps {
total += t;
}
println!("total {total}, average {:.1}", total as f64 / temps.len() as f64);
for (day, t) in temps.iter().enumerate() {
if *t > 16 {
println!("day {day} was warm: {t}");
}
}
}
total 73, average 14.6
day 3 was warm: 20
day 4 was warm: 17
for t in tempsvisits each element in order. No index, so no chance of an out-of-bounds index, and no bounds check needed at run time.temps.iter()produces a reference to each element instead of a copy:tis a&i32, a pointer to the element inside the array.*tfollows the pointer to get the value (the dereference operator). References are the subject of module 5; for an array of numbers either form works, but for large elements or elements you must not copy,.iter()is the way..enumerate()pairs each element with its index, giving tuples(index, element)that the pattern(day, t)unpacks.
You will often see the pattern for (i, &v) in values.iter().enumerate(): the &v in the pattern takes the reference apart so that v is a plain i32.
Use the index form only when you really need indexes for something else, such as comparing an element with its neighbor values[i - 1].
What for really does
for x in thing works with anything that can produce an iterator: a value with a method next() that returns the next item, or a signal that there are no more. The loop is shorthand for:
let mut it = thing.into_iter();
loop {
match it.next() {
Some(x) => { body using x }
None => break,
}
}
A range is its own iterator: next() returns the current number and advances. An array's iterator walks its elements. io::stdin().lines() from the last module is an iterator that reads a line each time next() is called. That is why the same for syntax works for all of them, and why you will be able to write your own iterators in module 11. Some, None and match are coming up very soon (the next lesson and module 8).
Accumulator patterns
Many loops follow one of a few shapes. Recognizing them makes loops easy to write:
fn main() {
let values = [4, -2, 9, 0, 9, 3];
let mut sum = 0; // accumulate
let mut count_positive = 0; // count matches
let mut max = values[0]; // track best so far
let mut first_zero = None; // find first match
for (i, &v) in values.iter().enumerate() {
sum += v;
if v > 0 {
count_positive += 1;
}
if v > max {
max = v;
}
if v == 0 && first_zero.is_none() {
first_zero = Some(i);
}
}
println!("sum {sum}, positive {count_positive}, max {max}, first zero {:?}", first_zero);
}
sum 23, positive 4, max 9, first zero Some(3)
Note how max starts at the first element rather than 0: with all-negative data, 0 would win incorrectly. first_zero uses Option (None or Some(index)), which you will meet properly in module 8.
Common mistakes
Off-by-one with ranges. 1..10 stops at 9. If you want 10, write 1..=10.
Modifying the array while iterating it. for t in temps.iter() { temps[0] = 1; } is rejected: you cannot change the array while something is reading through it. The error (E0506 or E0502) belongs to the borrowing rules of module 5, and it prevents a real class of bugs.
Using an index variable as the wrong type. Indexes are usize. for i in 0..n where n is an i32 gives an i32 that cannot index an array: "the type [i32] cannot be indexed by i32".
Expecting a reversed range to work without rev:
fn main() {
for i in 5..1 {
println!("{i}");
}
println!("done");
}
done
5..1 is simply empty. Count down with (1..5).rev(), which gives 4, 3, 2, 1.
Recap
for x in a..bcounts fromaup to but not includingb;a..=bincludesb..rev()and.step_by(n)change direction and stride.for x in arrayvisits elements;.iter()visits references;.enumerate()adds indexes.forworks with any iterator: something withnext()that eventually reports no more items.- Accumulate, count, track-the-best and find-first are the common loop shapes.
Going further: print the characters of a word with for c in "hello".chars() and their positions with .enumerate(). Try for (a, b) in [1, 2, 3].iter().zip([10, 20, 30].iter()) and guess what it does before running it.