What a nested loop is
A nested loop is a loop inside another loop. For every pass of the outer loop, the inner loop starts from the beginning and runs all of its passes before the outer loop moves on. If the outer loop runs 5 times and the inner loop runs 10 times per pass, the inner body runs 5 × 10 = 50 times.
Nested loops are the natural tool for two-dimensional data: rows and columns of a grid, a vector of vectors, or all pairs of values from a range. They are also the basis of brute-force solutions, which is where this chapter is heading.
The lesson's exercise: a rectangle of stars
The task is to print 5 rows of 10 stars. The structure mirrors the picture: the outer loop is over rows, the inner loop over the columns of one row.
- Write the outer loop
for row in 0..5, one pass per row. The range0..5includes 0 and excludes 5, so it gives exactly five values. - Write the inner loop inside it,
for col in 0..10, one pass per column. - Print one star in the inner loop with
print!("*"). Theprint!macro writes its text without a newline, so the stars of a row stay on the same line. - End the row with
println!()after the inner loop but still inside the outer one. A bareprintln!()prints just a newline, so the next row starts on a fresh line.
The solution in Rust
for row in 0..5 {
for col in 0..10 {
print!("*");
}
println!();
}
The output is:
**********
**********
**********
**********
**********
Compiled as written, the loop variables row and col are never used, so rustc prints an unused variable warning for each. A warning does not stop the program. Naming them _row and _col, or just _, silences it.
Placement of println!() matters. Inside the inner loop it would put every star on its own line. After the outer loop it would print a single long line of 50 stars and one newline at the end.
Going through a vector of vectors
With arr as a Vec of three Vecs of two numbers each, two loops visit every element:
let arr = vec![
vec![1, 2],
vec![3, 4],
vec![5, 6],
];
for i in 0..3 {
for j in 0..2 {
println!("{}", arr[i][j]);
}
}
This prints 1 to 6 on separate lines. The indices i and j are of type usize, which Rust requires for indexing. Going past the end panics instead of reading garbage: if the outer range were 0..4, the program would stop at the first access to arr[3] with index out of bounds: the len is 3 but the index is 3.
When you don't need the indices, iterate over the values directly, which avoids out-of-range mistakes:
for row in &arr {
for x in row {
print!("{} ", x);
}
}
This prints 1 2 3 4 5 6 .
break and continue in nested loops
A break leaves only the innermost loop that contains it. With break in the inner loop, the outer loop carries on:
for i in 0..3 {
for j in 0..2 {
if arr[i][j] == 3 {
break;
}
print!("{} ", arr[i][j]);
}
}
This prints 1 2 5 6 . When i is 1 and j is 0, the value 3 stops the inner loop for that row, so the 4 is skipped, but the outer loop continues with row 2.
A break placed directly in the outer loop ends everything. To stop both loops from inside the inner one, Rust lets you label the outer loop and name the label:
'outer: for i in 0..3 {
for j in 0..2 {
if arr[i][j] == 3 {
break 'outer;
}
print!("{} ", arr[i][j]);
}
}
This prints 1 2 and stops. A labelled continue 'outer works the same way for skipping to the outer loop's next pass. A plain continue skips the rest of the current pass of the innermost loop only.
Ranges for the inner loop
The inner range can depend on the outer variable. This visits each pair once, with the second value never smaller than the first:
for i in 1..=3 {
for j in i..=3 {
print!("({},{}) ", i, j);
}
}
It prints (1,1) (1,2) (1,3) (2,2) (2,3) (3,3) . The syntax a..=b includes b, while a..b does not.
Common mistakes
- Off-by-one ranges.
0..5has five values, 0 to 4. Use0..=5only when you want six. - Using
breakto leave both loops. It leaves one. Use a label, or move the loops into a function andreturn. - Reusing the same variable name in both loops. The inner
iwould shadow the outer one, which is legal but confusing. - Counting the cost. Two nested loops over
nvalues runn * ntimes. Ten thousand values means a hundred million passes.
Key takeaways
- The inner loop restarts and finishes completely on every pass of the outer loop.
print!keeps the output on the line, andprintln!()ends it.breakaffects the innermost loop;break 'labelreaches an outer one.- Indexing uses
usizeand panics when out of bounds.
