3
for x in line.x1..=line.x2 {
    ...
}

This doesn't work for cases where x1 > x2, so I use this workaround:

for x in (cmp::min(line.x1, line.x2))..=(cmp::max(line.x1, line.x2)) {
    ...
}

This was fine until I needed to iterate through two fields in tandem:

for (x, y) in (line.x1..=line.x2).zip((line.y1..=line.y2)) {
    ...
}

Here my previous trick cannot work.

Is there an idiomatic way to use ranges where the start value may be greater than the end value?


Solution based on Brian's answer:

fn range_inclusive(a: usize, b: usize) -> impl Iterator<Item = usize> {
    let x: Box<dyn Iterator<Item = usize>>;
    if b > a {
        x = Box::new(a..=b)
    } else {
        x = Box::new((b..=a).rev())
    }
    x
}

fn main() {
    for i in range_inclusive(3, 1).zip(range_inclusive(1, 3)) {
        println!("{:?}", i);
    }
}
Herohtar
  • 5,347
  • 4
  • 31
  • 41
Krish
  • 1,044
  • 9
  • 20

2 Answers2

4

The simplest way would be to reverse the range that you need:

for i in (0..11).rev() {
    println!("{}", i);
}

will print 10 to 0 in decreasing order.

Jeremy Meadows
  • 2,314
  • 1
  • 6
  • 22
  • 1
    After re-reading your code example, it looks like you may want to handle forward *and* backward ranges with the same code, so this wouldn't work for that. – Jeremy Meadows Dec 13 '21 at 05:28
  • Not quite ideal, but good enough. To avoid fighting the type system, I ended up repeating myself: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2f84ff9087fc82c334a2ba4926e9e2d6) – Krish Dec 13 '21 at 05:46
  • @Krish, that is horrible :( – hkBst Dec 13 '21 at 15:45
  • @hkBst I completely agree – Krish Dec 14 '21 at 10:54
1

I don't know if this is really better than the snippet you posted in the other comment thread, but this may also work depending on your circumstances.

Edit: the following code includes refinements from @Krish. See also his playground for it.

fn range_inclusive(a: usize, b: usize) -> impl Iterator<Item = usize> {
    let x: Box<dyn Iterator<Item = usize>>;
    if b > a {
        x = Box::new(a..=b)
    } else {
        x = Box::new((b..=a).rev())
    }
    x
}

For posterity, here's what I had originally posted, prior to Krish's improvements.

fn get_range_iter_inclusive(a: usize, b: usize) -> impl Iterator<Item = usize> {
    if b > a {
        let vec: Vec<usize> = (a..=b).collect();
        vec.into_iter()
    } else {
        let vec: Vec<usize> = (b..=a).rev().collect();
        vec.into_iter()
    }
}

Materializing the ranges into Vec<_> is obviously bad for runtime performance in some cases. This was the only way I could figure out to beat the type system into submission. But I'm just learning Rust, so there could very well be a better way.

As an aside, I'm still struggling to understand why creating a descending range should not only fail, but fail silently.

Brian Reischl
  • 7,216
  • 2
  • 35
  • 46
  • 1
    I didn't think of using `impl Iterator` , thanks. Here's a more performant version of your code that uses trait objects: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ac1b1541d7aa326cf1dfa23afeaa5338) – Krish Dec 15 '21 at 05:26
  • The boxing makes that much nicer! – Brian Reischl Dec 15 '21 at 14:59