Neil Macneale
Neil Macneale

Reputation: 341

Rust impl Iterator for Range<T> on custom type

I have a point struct, like so:

#[derive(Clone, Copy, Debug)]
struct Point {
  x: i32,
  y: i32,
}

I would like to iterate between points like so:

let start = Point { x: 0, y: 0 };
let end = Point { x: 3, y: 3 };
for p in start..end {
  // p should be
  // (0, 0)
  // (1, 0)
  // (2, 0)
  // (0, 1)
  // (1, 1)
  // (2, 1)
  // (0, 2)
  // etc
}

Playground.

However, I cannot seem to implement this. start..end produces a Range<Pos>, which is a type in the standard library. So I cannot make a custom impl Iterator for Range<Pos>, as none of those types are in the current crate.

I also cannot impl Step for Point, as the iterator must know the start and end of the range. Step does not give you this information.

Lastly, I cannot write a wrapper for Iterator, as impl MyIterator for Range<Point> will fail with an error saying that Step is not implemented for Point.

Is there any way to solve this? My current solution is to define a to function on a Point, so that you can write this:

for p in start.to(end) {
  // works as expected
}

The to function produces a custom iterator which does what I'm looking for. However, I still would like to use the .. syntax.

EDIT: AFAIK, this is what is needed for this to work. Based on these comments, this looks impossible.

Upvotes: 5

Views: 2348

Answers (1)

Peter Hall
Peter Hall

Reputation: 58805

You can't do this directly, as you've discovered. However I wouldn't get too hung up on the syntax, as point..point doesn't necessarily make the code more readable. In fact it hides information about the ordering and makes it more difficult to later add an iterator that goes in column order instead of rows.

A design that communicates the order, while still using the range syntax could be:

fn row_major(range: Range<Point>) -> impl Iterator<Item = Point> {
    (range.start.y..range.end.y)
        .flat_map(move |y| (range.start.x..range.end.x).map(move |x| Point { x, y }))
}

fn main() {
    let start = Point { x: 0, y: 0 };
    let end = Point { x: 2, y: 2 };
    let result: Vec<_> = row_major(start..end).collect();

    assert_eq!(
        result,
        vec![
            Point { x: 0, y: 0 },
            Point { x: 1, y: 0 },
            Point { x: 0, y: 1 },
            Point { x: 1, y: 1 },
        ]
    )
}

row_major may not be the best name, depending on your application. Other options are things like horizontal_iter, hrange or iter_rows. Giving it a name like that makes it clear to a reader what order to expect and also gives the opportunity to add a symmetric column-major (vertical) version of the function later.

You could also extend this to support ..end and start..=end variants for more expressivity, while excluding start.. which doesn't make sense here.

Upvotes: 11

Related Questions