Question
How can I iterate over a range in Rust using a step value other than 1?
I am coming from a C++ background, where I might write a loop like this:
for (auto i = 0; i <= n; i += 2) {
// ...
}
In Rust, I typically use ranges such as 0..=n, but I do not see a third argument for specifying a custom step size. What is the idiomatic way to do this in Rust?
Short Answer
By the end of this page, you will understand how Rust ranges work, why they do not directly include a step argument, and how to use iterator methods such as step_by() to loop with custom increments. You will also learn when inclusive and exclusive ranges matter, how to trace execution, and what common mistakes to avoid.
Concept
Rust treats ranges as iterators or values that can become iterators, and iteration behavior is built up by chaining iterator methods.
Unlike C++'s classic for loop syntax, Rust does not usually put initialization, condition, and increment in one line. Instead, Rust encourages you to describe:
- what sequence you want
- how that sequence should be transformed
- what to do with each item
For a custom step size, the key idea is that you start with a range and then apply .step_by(step).
For example:
for i in (0..=n).step_by(2) {
println!("{}", i);
}
This means:
- start at
0 - go up to and including
n - take every 2nd value
This matters in real programming because many tasks do not move one item at a time. You may need to:
- process every other index
- sample data at intervals
- skip unused values
- generate sequences like
0, 5, 10, 15
Rust's iterator-based approach is powerful because the same style works for many kinds of sequences, not just numeric loops.
Mental Model
Think of a Rust range like a numbered path:
0..=10means all stepping stones from0through10.step_by(2)means you choose to land on every second stone
So instead of saying, "increase i by 2 after each loop," Rust says, "build me a sequence that already contains only the values I want."
That is the mindset shift from C++-style looping to Rust-style iteration:
- C++ often focuses on manually controlling the counter
- Rust often focuses on describing the values to iterate over
Syntax and Examples
Basic syntax
for i in (start..end).step_by(step) {
// use i
}
For an inclusive end:
for i in (start..=end).step_by(step) {
// use i
}
Example: even numbers up to n
fn main() {
let n = 10;
for i in (0..=n).step_by(2) {
println!("{}", i);
}
}
Output:
0
2
4
6
8
10
Explanation
0..=ncreates an inclusive range from0ton.step_by(2)skips every other numberforloops over the resulting iterator
Example: exclusive upper bound
fn main() {
for i in (0..10).step_by(3) {
println!("{}", i);
}
}
Output:
0
3
6
9
Here, 10 is not included because 0..10 is exclusive at the end.
Example: starting from a non-zero value
fn main() {
for i in (5..=15).step_by(5) {
println!("{}", i);
}
}
Output:
5
10
15
Step by Step Execution
Consider this code:
fn main() {
let n = 8;
for i in (0..=n).step_by(2) {
println!("value = {}", i);
}
}
Here is what happens step by step:
-
let n = 8;- A variable
nis created with value8.
- A variable
-
(0..=n)- Rust creates an inclusive range:
0, 1, 2, 3, 4, 5, 6, 7, 8
- Rust creates an inclusive range:
-
.step_by(2)- Rust transforms that sequence to take every 2nd value starting from the first one.
- The resulting values are:
0, 2, 4, 6, 8
-
for i in ...- The loop runs once for each value in that stepped sequence.
-
Loop iterations:
- First iteration:
i = 0 - Second iteration:
i = 2 - Third iteration:
i = 4 - Fourth iteration:
i = 6 - Fifth iteration:
i = 8
- First iteration:
-
After
8- There are no more values in the iterator, so the loop ends.
This is conceptually similar to:
for (int i = 0; i <= 8; i += 2)
But in Rust, the sequence is described first, and the loop simply consumes it.
Real World Use Cases
Custom-step iteration appears often in practical Rust code.
Processing every nth item
You may want to sample data instead of reading every element.
let data = [10, 20, 30, 40, 50, 60];
for i in (0..data.len()).step_by(2) {
println!("{}", data[i]);
}
Working with grid coordinates
In graphics, game development, or simulations, you might move through positions in fixed intervals.
for x in (0..=100).step_by(10) {
println!("x = {}", x);
}
Reading structured data
If bytes are grouped in chunks, a stepped index can help.
let bytes = [1, 2, 3, 4, 5, 6, 7, 8];
for i in (0..bytes.len()).step_by(2) {
println!("pair starts at index {}", i);
}
Scheduling repeated intervals
Stepped ranges are useful for generating fixed time slots, IDs, or batch boundaries.
for minute in (0..60).step_by(15) {
println!("Run task at minute {}", minute);
}
Real Codebase Usage
In real projects, developers often combine step_by() with other iterator patterns.
Looping over indices carefully
When working with arrays or slices, stepped loops are often used with lengths:
let items = ["a", "b", "c", "d", "e"];
for i in (0..items.len()).step_by(2) {
println!("{} -> {}", i, items[i]);
}
This is common when you specifically need the index.
Using guard clauses before looping
If the step depends on input, validate it first.
fn print_with_step(n: usize, step: usize) {
if step == 0 {
return;
}
for i in (0..=n).step_by(step) {
println!("{}", i);
}
}
This avoids invalid logic and makes the function safer.
Prefer direct iteration when possible
In production Rust, developers often avoid indexing unless needed. If you need every second element from a collection, another approach may be clearer depending on the situation.
let values = [10, 20, 30, 40, 50, 60];
for value in values.iter().step_by(2) {
println!("{}", value);
}
This is often safer because it avoids manual index access.
Combining with filters and maps
Stepped iteration is often only one part of a pipeline.
for x in (0..=20).step_by(2).map(|n| n * n) {
println!("{}", x);
}
This style is common in Rust codebases because iterator chains are expressive and composable.
Common Mistakes
1. Forgetting parentheses around the range
Broken:
for i in 0..=10.step_by(2) {
println!("{}", i);
}
Why it is wrong:
- Method call precedence does not mean what you want here.
- Rust may try to call
step_byon10instead of on the range.
Correct:
for i in (0..=10).step_by(2) {
println!("{}", i);
}
2. Confusing .. with ..=
Broken expectation:
for i in (0..10).step_by(2) {
println!("{}", i);
}
A beginner may expect 10 to be included, but it is not.
0..10means0through90..=10means0through10
3. Using a step of zero
Broken:
for i in (0..=10).step_by(0) {
println!("{}", i);
}
Why it is wrong:
- A step of
0is invalid. - You should ensure the step is at least
1.
Safer pattern:
fn loop_values(limit: usize, step: usize) {
if step == 0 {
println!("step must be greater than 0");
return;
}
for i in (0..=limit).step_by(step) {
println!("{}", i);
}
}
4. Expecting negative steps with normal ranges
Rust ranges like 0..10 move forward. If you want to go backward, use .rev() when appropriate.
Example:
for i in (0..=10).rev().step_by(2) {
println!("{}", i);
}
5. Indexing when direct iteration is clearer
Less idiomatic for simple access:
let values = [10, 20, 30, 40];
for i in (0..values.len()).step_by(2) {
println!("{}", values[i]);
}
Often better:
let values = [10, 20, 30, 40];
for value in values.iter().step_by(2) {
println!("{}", value);
}
Use indices only when you actually need them.
Comparisons
Custom-step range vs manual counter loop
| Approach | Style | Best for | Example |
|---|---|---|---|
Range + step_by() | Idiomatic Rust | Clear numeric iteration | (0..=n).step_by(2) |
while loop with manual increment | More manual | Complex loop control | i += 2 inside loop |
for with step_by() vs while
| Feature | for + step_by() | while |
|---|---|---|
| Readability | Usually clearer | More verbose |
| Manual control | Less | More |
| Risk of forgetting increment | None | Higher |
| Idiomatic Rust | Yes | Sometimes |
Example: same logic in two styles
Idiomatic Rust
for i in (0..=10).step_by(2) {
println!("{}", i);
}
Manual while
fn main() {
let mut i = 0;
let n = 10;
while i <= n {
println!("{}", i);
i += 2;
}
}
Use while when the loop condition is more complicated than a simple sequence. Use for with step_by() when you are iterating through a well-defined range.
Index stepping vs iterator stepping
| Pattern | Example | When to use |
|---|---|---|
| Step through indices | (0..items.len()).step_by(2) | You need positions or multiple arrays |
| Step through values directly | items.iter().step_by(2) | You only need the elements |
Cheat Sheet
Quick syntax
for i in (start..end).step_by(step) {
// end is excluded
}
for i in (start..=end).step_by(step) {
// end is included
}
Common examples
for i in (0..10).step_by(2) {}
for i in (0..=10).step_by(2) {}
for x in values.iter().step_by(2) {}
Rules to remember
- Use
.step_by(n)to skip byn - Add parentheses around the range:
(0..10).step_by(2) ..is exclusive at the end..=is inclusive at the endstepmust be greater than0- Prefer iterating over values directly if you do not need indices
Reverse iteration
for i in (0..=10).rev().step_by(2) {
println!("{}", i);
}
C++ mental translation
C++:
for (int i = 0; i <= n; i += 2)
Rust:
for i in (0..=n).step_by(2) {
// ...
}
FAQ
How do I increment by 2 in a Rust for loop?
Use .step_by(2) on a range:
for i in (0..=10).step_by(2) {
println!("{}", i);
}
How do I include the end value in a Rust range?
Use ..= instead of ...
0..=10
This includes 10.
Can I use a negative step in Rust ranges?
Not directly with a normal forward range. For reverse iteration, use .rev() on a range and then step through it.
Why does Rust use step_by() instead of a third range argument?
Rust's iteration model is based on iterators and iterator methods. This keeps ranges simple and lets you build custom behavior by chaining methods.
Should I use indices or iterate over values directly?
If you only need the elements, iterate over values directly:
for value in items.iter().step_by(2) {
println!("{}", value);
}
Use indices when you need positions.
What happens if the step does not divide the range evenly?
Rust stops when the next stepped value would go past the end. It does not force the final value to match the boundary.
Mini Project
Description
Build a small Rust program that prints numbered checkpoints for a training session. The checkpoints should be generated at a fixed interval, such as every 5 minutes. This demonstrates how to use a range with step_by() in a realistic scheduling-style task.
Goal
Create a Rust program that prints all checkpoint times from 0 up to a limit using a custom step size.
Requirements
- Read or define a total session length as an integer
- Read or define a checkpoint interval greater than 0
- Print each checkpoint from 0 up to the session length
- Include the final checkpoint only if it falls exactly on the interval
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.