Is there thing like abstract iterator in rust?

I write tetris game in rust with the single purpose of learning rust. Anyway
I have function check_collision which take array of points(4) as array or more likely struct that implement into_iter. I want to reuse the same function for collapsing blocks after line deletion. Those block contain vector with unrestricted size. How I can redefine function to take abstract iterator over some fixed type element (Point)

You're looking for the Iterator trait.

You can write your function like this:

fn check_collision(points: impl Iterator<Item = Point>, other: Point) -> bool {
    /* */
}

check_collision([a, b, c, d].iter(), e);

Or, if you'd rather be more generic, you could also write it as IntoIterator instead:

fn check_collision(points: impl IntoIterator<Item = Point>, other: Point) -> bool {
    for point in points.into_iter() {
        /* */
    }
}

Note: a bunch of things implement IntoIterator, including anything which is already an Iterator. So, slices, arrays, Vecs, HashMaps, HashSets, etc. will all work for this, including any iterator magic you could do on them.

2 Likes

If the data is contiguous in memory, another option is to return a slice (&[Point]). It's the lowest common denominator type for all array-like containers. It gives you Iterator support for free.

Thanks. Your answer is very close to whats I need. Unfortunately for me vector iterator is like Iterator<Item = &Point> and my into_iter was like Iterator<Item = Point>. I tried to fix that incompatibility by changing my implementation to Iterator<Item = &Point>
Bellow is my new implementation and the error that I can't even get

pub struct TetrominoRotation {
    pub offset: Point,
    pub sequence: TetrominoSequence,
}

impl<'a> IntoIterator for &'a TetrominoRotation {
    type Item = &'a Point;
    type IntoIter = TetrominoRotationIterator<'a>;

    fn into_iter(self) -> Self::IntoIter {
        Self::IntoIter {
            index: 0,
            payload: self,
            temp_result: Point { x: 0, y: 0 },
        }
    }
}

pub struct TetrominoRotationIterator<'a> {
    index: usize,
    payload: &'a TetrominoRotation,
    temp_result: Point,
}

impl<'a> Iterator for TetrominoRotationIterator<'a> {
    type Item = &'a Point;
    fn next(&mut self) -> Option<Self::Item> {
        if self.index < TETRAMINO_POINTS_COUNT {
            self.temp_result = self.payload.sequence[self.index].add(&self.payload.offset);
            self.index += 1;
            return Some(&self.temp_result);
        }

        None
    }
}
error[E0515]: cannot return value referencing temporary value
   --> src/tetramino.rs:106:20
    |
104 |             let temp_result = &self.payload.sequence[self.index].add(&self.payload.offset);
    |                                ----------------------------------------------------------- temporary value created here
105 |             self.index += 1;
106 |             return Some(&temp_result);
    |                    ^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0515`.
error: could not compile `tetris`.

To learn more, run the command again with --verbose.
kalin@kalin-desktop:~/Projects/tetris$ cargo build
   Compiling tetris v0.1.0 (/home/kalin/Projects/tetris)
warning: unused variable: `data`
 --> src/chunk.rs:9:16
  |
9 |     pub fn new(data: Vec<Point>) -> Self {
  |                ^^^^ help: if this is intentional, prefix it with an underscore: `_data`
  |
  = note: `#[warn(unused_variables)]` on by default

error[E0515]: cannot return value referencing temporary value
   --> src/tetramino.rs:105:20
    |
105 |             return Some(&self.payload.sequence[self.index].add(&self.payload.offset));
    |                    ^^^^^^-----------------------------------------------------------^
    |                    |     |
    |                    |     temporary value created here
    |                    returns a value referencing data owned by the current function

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0515`.
error: could not compile `tetris`.

To learn more, run the command again with --verbose.
kalin@kalin-desktop:~/Projects/tetris$ cargo build
   Compiling tetris v0.1.0 (/home/kalin/Projects/tetris)
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
   --> src/tetramino.rs:106:25
    |
106 |             return Some(&self.temp_result);
    |                         ^^^^^^^^^^^^^^^^^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 102:5...
   --> src/tetramino.rs:102:5
    |
102 | /     fn next(&mut self) -> Option<Self::Item> {
103 | |         if self.index < TETRAMINO_POINTS_COUNT {
104 | |             self.temp_result = self.payload.sequence[self.index].add(&self.payload.offset);
105 | |             self.index += 1;
...   |
109 | |         None
110 | |     }
    | |_____^
note: ...so that reference does not outlive borrowed content
   --> src/tetramino.rs:106:25
    |
106 |             return Some(&self.temp_result);
    |                         ^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 100:6...
   --> src/tetramino.rs:100:6
    |
100 | impl<'a> Iterator for TetrominoRotationIterator<'a> {
    |      ^^
note: ...so that the types are compatible
   --> src/tetramino.rs:102:46
    |
102 |       fn next(&mut self) -> Option<Self::Item> {
    |  ______________________________________________^
103 | |         if self.index < TETRAMINO_POINTS_COUNT {
104 | |             self.temp_result = self.payload.sequence[self.index].add(&self.payload.offset);
105 | |             self.index += 1;
...   |
109 | |         None
110 | |     }
    | |_____^
    = note: expected `std::iter::Iterator`
               found `std::iter::Iterator`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.
error: could not compile `tetris`.

To learn more, run the command again with --verbose.
kalin@kalin-desktop:~/Projects/tetris$ cargo build
   Compiling tetris v0.1.0 (/home/kalin/Projects/tetris)
warning: unused variable: `data`
 --> src/chunk.rs:9:16
  |
9 |     pub fn new(data: Vec<Point>) -> Self {
  |                ^^^^ help: if this is intentional, prefix it with an underscore: `_data`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: field is never read: `position`
 --> src/chunk.rs:4:5
  |
4 |     position: Point,
  |     ^^^^^^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: 2 warnings emitted

    Finished dev [unoptimized + debuginfo] target(s) in 2.72s
kalin@kalin-desktop:~/Projects/tetris$ cargo build
   Compiling tetris v0.1.0 (/home/kalin/Projects/tetris)
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
   --> src/tetramino.rs:106:25
    |
106 |             return Some(&self.temp_result);
    |                         ^^^^^^^^^^^^^^^^^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 102:5...
   --> src/tetramino.rs:102:5
    |
102 | /     fn next(&mut self) -> Option<Self::Item> {
103 | |         if self.index < TETRAMINO_POINTS_COUNT {
104 | |             self.temp_result = self.payload.sequence[self.index].add(&self.payload.offset);
105 | |             self.index += 1;
...   |
109 | |         None
110 | |     }
    | |_____^
note: ...so that reference does not outlive borrowed content
   --> src/tetramino.rs:106:25
    |
106 |             return Some(&self.temp_result);
    |                         ^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 100:6...
   --> src/tetramino.rs:100:6
    |
100 | impl<'a> Iterator for TetrominoRotationIterator<'a> {
    |      ^^
note: ...so that the types are compatible
   --> src/tetramino.rs:102:46
    |
102 |       fn next(&mut self) -> Option<Self::Item> {
    |  ______________________________________________^
103 | |         if self.index < TETRAMINO_POINTS_COUNT {
104 | |             self.temp_result = self.payload.sequence[self.index].add(&self.payload.offset);
105 | |             self.index += 1;
...   |
109 | |         None
110 | |     }
    | |_____^
    = note: expected `std::iter::Iterator`
               found `std::iter::Iterator`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.
error: could not compile `tetris`.

The Iterator interface promises that this is valid code:

let iter = x.into_iter();
let a = iter.next();
let b = iter.next();
drop(iter);
use_both(a, b);

which means the iterator is forbidden from returning references to itself, because it's always valid to destroy the iterator and its contents before using any elements it has returned.

So if your iterator owns the points, it must give them away as owned (can't be the place that holds them).

If your iterator doesn't own the points, only borrows then them, then it can give out temporary references, because the temporary lifetime is not the iterator's lifetime (iterator can come and go independently, because the points are stored elsewhere).

Note that in Rust the & syntax is not about avoiding copies, it's about ownership. When you add & you're not just changing by-value to by-reference, you're changing owned to temporarily borrowed (e.g. Box<Point> is owned by reference).

2 Likes

The alternative StreamingIterator returns referenced items by design, but you lose any algorithm that wants more than one item at a time, like collect, min/max, etc.

2 Likes

I just implement into iterator for the other structure. I will refactor it later. It is boring problem to fight

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.