241

How can I iterate over a range in Rust with a step other than 1? I'm coming from a C++ background so I'd like to do something like

for(auto i = 0; i <= n; i+=2) {
    //...
}

In Rust I need to use the range function, and it doesn't seem like there is a third argument available for having a custom step. How can I accomplish this?

6 Answers 6

367

range_step_inclusive and range_step are long gone.

As of Rust 1.28, Iterator::step_by is stable:

fn main() {
    for x in (1..10).step_by(2) {
        println!("{}", x);
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Note that this method will not support 64 bit step on machines with 32 bit size type.
And if the step is half the value?
Unfortunately, this doesn't nicely handle negative steps. Where you'd like to do, say, (11..=1).step_by(-2) (odd numbers counting down from 11 to 1, inclusive), you have to do (1..12).step_by(2).rev(), which is not quite as intuitive.
16

It seems to me that until the .step_by method is made stable, one can easily accomplish what you want with an Iterator (which is what Ranges really are anyway):

struct SimpleStepRange(isize, isize, isize);  // start, end, and step

impl Iterator for SimpleStepRange {
    type Item = isize;

    #[inline]
    fn next(&mut self) -> Option<isize> {
        if self.0 < self.1 {
            let v = self.0;
            self.0 = v + self.2;
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    for i in SimpleStepRange(0, 10, 2) {
        println!("{}", i);
    }
}

If one needs to iterate multiple ranges of different types, the code can be made generic as follows:

use std::ops::Add;

struct StepRange<T>(T, T, T)
    where for<'a> &'a T: Add<&'a T, Output = T>,
          T: PartialOrd,
          T: Clone;

impl<T> Iterator for StepRange<T>
    where for<'a> &'a T: Add<&'a T, Output = T>,
          T: PartialOrd,
          T: Clone
{
    type Item = T;

    #[inline]
    fn next(&mut self) -> Option<T> {
        if self.0 < self.1 {
            let v = self.0.clone();
            self.0 = &v + &self.2;
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    for i in StepRange(0u64, 10u64, 2u64) {
        println!("{}", i);
    }
}

I'll leave it to you to eliminate the upper bounds check to create an open ended structure if an infinite loop is required...

Advantages of this approach is that is works with for sugaring and will continue to work even when unstable features become usable; also, unlike the de-sugared approach using the standard Ranges, it doesn't lose efficiency by multiple .next() calls. Disadvantages are that it takes a few lines of code to set up the iterator so may only be worth it for code that has a lot of loops.

2 Comments

By adding another type, U to your second option you could use types that support addition with a different type and still yield a T. For instance time and duration come to mind.
@Ryan, that seems like a good idea and should work, with the struct defined as follows: struct StepRange<T>(T, T, U) where for<'a, 'b> &'a T: Add<&'b U, Output = T>, T: PartialOrd, T: Clone; which should allow different lifetimes for the input T and U types.
6

Use the num crate with range_step

Comments

5

If you are stepping by something predefined, and small like 2, you may wish to use the iterator to step manually. e.g.:

let mut iter = 1..10;
loop {
    match iter.next() {
        Some(x) => {
            println!("{}", x);
        },
        None => break,
    }
    iter.next();
}

You could even use this to step by an arbitrary amount (although this is definitely getting longer and harder to digest):

let mut iter = 1..10;
let step = 4;
loop {
    match iter.next() {
        Some(x) => {
            println!("{}", x);
        },
        None => break,
    }
    for _ in 0..step-1 {
        iter.next();
    }
}

1 Comment

Why use this over for and step_by?
4

You'd write your C++ code:

for (auto i = 0; i <= n; i += 2) {
    //...
}

...in Rust like so:

let mut i = 0;
while i <= n {
    // ...
    i += 2;
}

I think the Rust version is more readable too.

2 Comments

Re: inserting "continue" in the loop, one would only do this inside a conditional branch even in the for structure, I think. If so, then I think it would be OK to increment inside the conditional branch in the while structure before "continue"-ing, and that it would then work as intended. Or am I overlooking something?
@WDS that's counterintuitive busywork to get a basic feature of the language, continue, to work properly. Though it can be done, this design encourages bugs.
0

In my case I was wanting my step size to be a float value and Iterator::step_by is a usize...

The solution I'm doing at the moment is utilizing my own struct but I'm kind of surprised this is necessary.

struct FloatRange {
    start: f64,
    end: f64,
    step: f64,
    current: f64,
    inclusive: bool,
    precision: usize,
}

impl FloatRange {
    fn new(start: f64, end: f64, step: f64, inclusive: bool, precision: usize) -> Self {
        FloatRange {
            start,
            end,
            step,
            current: start,
            inclusive: inclusive,
            precision: precision,
        }
    }

    fn round_to_precision(value: f64, precision: usize) -> f64 {
        let factor = 10f64.powi(precision as i32);
        (value * factor).round() / factor
    }
}

impl Iterator for FloatRange {
    type Item = f64;

    fn next(&mut self) -> Option<Self::Item> {
        if self.current > self.end {
            None
        } else if !self.inclusive && self.current == self.end {
            None
        } else {
            let current = self.current;
            self.current = FloatRange::round_to_precision(self.current + self.step, self.precision);
            Some(current)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_float_range_noninclusive() {
        let range = FloatRange::new(-1.0, 1.0, 0.2, false, 2);
        let result: Vec<f64> = range.collect();

        let expected = vec![-1.0, -0.8, -0.6, -0.4, -0.2, 0.0, 0.2, 0.4, 0.6, 0.8];
        assert_eq!(result, expected);
    }

    #[test]
    fn test_float_range_inclusive() {
        let range = FloatRange::new(-1.0, 1.0, 0.2, true, 2);
        let result: Vec<f64> = range.collect();

        let expected = vec![-1.0, -0.8, -0.6, -0.4, -0.2, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
        assert_eq!(result, expected);
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.