Reputation: 341
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
}
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
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