Iterating over Ranges
A range in Rust is a compact iterator object that generates a sequence of values from a start bound to an end bound on the fly without allocating an array or vector in memory. Combining a for loop with a range (start..end or start..=end) is the standard way to repeat an action a fixed number of times or step through numeric indices.
Half-Open (..) vs Inclusive (..=) Ranges
Rust provides two range operators that can be used directly in a for loop:
| Syntax | Type (std::ops) |
Bounds Included | Values Yielded by 1..4 / 1..=4 |
Iteration Count (start <= end) |
|---|---|---|---|---|
start..end |
Range<T> |
Half-open (includes start, excludes end) |
1, 2, 3 |
end - start |
start..=end |
RangeInclusive<T> |
Closed / Inclusive (includes both start and end) |
1, 2, 3, 4 |
end - start + 1 |
Half-Open Range (0..4):
[ 0 ] ---> [ 1 ] ---> [ 2 ] ---> [ 3 ] (stops BEFORE 4; yields 4 items)
Inclusive Range (0..=4):
[ 0 ] ---> [ 1 ] ---> [ 2 ] ---> [ 3 ] ---> [ 4 ] (includes 4; yields 5 items)
fn main() {
print!("Half-open (1..5): ");
for n in 1..5 {
print!("{n} ");
}
println!();
print!("Inclusive (1..=5): ");
for n in 1..=5 {
print!("{n} ");
}
println!();
}
Half-open (1..5): 1 2 3 4
Inclusive (1..=5): 1 2 3 4 5
Why 0..n Repeats Exactly n Times
Because a half-open range 0..n starts at 0 and stops just before n, it yields 0, 1, ..., n - 1—which is always exactly n iterations and matches valid 0-based indices for a slice of length n:
fn main() {
// Repeat an action exactly 3 times (ignoring the counter with `_`)
for _ in 0..3 {
println!("Ping!");
}
}
Ping!
Ping!
Ping!
Why Inclusive ..= Matters at MAX Bounds
To iterate up to the maximum possible value of an integer type (such as u8::MAX, which is 255), a half-open range 0..256 cannot be written as a u8 range because 256 overflows u8. Using an inclusive range 0..=u8::MAX (0_u8..=255_u8) covers all 256 values cleanly without overflow.
Empty Ranges (start >= end)
A range in Rust only counts upward by default. If start >= end in start..end (or start > end in start..=end), the range is empty and the for loop executes zero times:
fn main() {
// `5..1` is an empty range because 5 >= 1!
for n in 5..1 {
println!("Never printed: {n}");
}
println!("Loop over 5..1 finished with 0 iterations.");
}
Loop over 5..1 finished with 0 iterations.
Counting Backward with .rev()
To iterate over a range in descending (reverse) order, write the range in normal ascending order (low..high or low..=high) and call .rev() on it:
fn main() {
print!("Countdown (1..=5).rev(): ");
for t in (1..=5).rev() {
print!("{t} ");
}
println!();
print!("Reverse indices (0..4).rev(): ");
for idx in (0..4).rev() {
print!("{idx} ");
}
println!();
}
Countdown (1..=5).rev(): 5 4 3 2 1
Reverse indices (0..4).rev(): 3 2 1 0
Note: The parentheses around
(1..=5).rev()are mandatory because the method-call dot operator (.) has higher precedence than the range operator (..=). Without parentheses,1..=5.rev()would attempt to call.rev()on the integer5!
Custom Step Sizes with .step_by(n)
By default, a range increments by 1 on each step. Call .step_by(n) (where n >= 1) to advance by n elements per iteration:
fn main() {
print!("Even numbers in 0..=10: ");
for even in (0..=10).step_by(2) {
print!("{even} ");
}
println!();
print!("Counting down by 10s: ");
for tens in (0..=50).rev().step_by(10) {
print!("{tens} ");
}
println!();
}
Even numbers in 0..=10: 0 2 4 6 8 10
Counting down by 10s: 50 40 30 20 10 0
Passing 0 to .step_by(0) panics at runtime (assertion failed: step != 0).
Iterating over Character Ranges ('a'..='z')
In addition to integer types, char also supports range iteration! A char range steps through consecutive Unicode scalar values:
fn main() {
print!("Lowercase ASCII: ");
for ch in 'a'..='z' {
print!("{ch}");
}
println!();
print!("Hex digits: ");
for ch in ('0'..='9').chain('A'..='F') {
print!("{ch} ");
}
println!();
}
Lowercase ASCII: abcdefghijklmnopqrstuvwxyz
Hex digits: 0 1 2 3 4 5 6 7 8 9 A B C D E F
Why Floating-Point Ranges Cannot Be Iterated Directly
While 0.0..1.0 is a valid Range<f64> value for bounds checking ((0.0..1.0).contains(&0.5)), Range<f64> does not implement Iterator because there is no natural "next step" size (1.0? 0.1? f64::EPSILON?) and repeated floating-point addition accumulates rounding error.
Instead, iterate over an integer range and multiply to compute each floating-point step cleanly:
fn main() {
// Generate 0.0, 0.25, 0.50, 0.75, 1.00 without cumulative float drift
for step in 0..=4 {
let value = f64::from(step) * 0.25;
println!("Step {step}: {value:.2}");
}
}
Step 0: 0.00
Step 1: 0.25
Step 2: 0.50
Step 3: 0.75
Step 4: 1.00