Control Flow

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 integer 5!

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