Trait, IntoIterator and lifetime

Hi everyone!

I have a working example for a struct that implements the IntoIterator trait (let me know if there are any errors here.)
Example without trait

But now, I would like to create a trait defining the contract to let a developer create custom structs.
I would like the trait to enforce the implementation of IntoIterator for a reference of the struct in a specific way (Item should be (usize, usize, bool))

For what I understand it's a problem of lifetime, because if I change my code and implements the trait for Matrix and not &'a Matrix it works Example with Matrix

What do I need to do? And is it the good way to do it?

Thanks.

fn main() {
    let mut matrix = Matrix::new();
    let mut cells: Vec<_> = matrix.into_iter().collect();

    display_matrix(&matrix);

    let indexes: [usize; 3] = [1, 1, 2];
    for index in indexes.into_iter() {
        let cell = cells.remove(*index);
        &matrix.set_value(cell.0, cell.1, true);
    }

    display_matrix(&matrix);
}

trait MatrixTrait
where
    Self: IntoIterator<Item = (usize, usize, bool)>,
{
    fn new() -> Self;
    fn get_value(&self, x: usize, y: usize) -> bool;
    fn set_value(&mut self, x: usize, y: usize, value: bool);
}

const WIDTH: usize = 3;
const HEIGHT: usize = 2;

struct Matrix {
    matrix: [[bool; HEIGHT]; WIDTH],
}

impl MatrixTrait for Matrix {
    fn new() -> Self {
        Self {
            matrix: [[false; HEIGHT]; WIDTH],
        }
    }

    fn get_value(&self, x: usize, y: usize) -> bool {
        if self.is_x_y_valid(x, y) {
            self.matrix[x][y]
        } else {
            panic!("x and y are not valid");
        }
    }

    fn set_value(&mut self, x: usize, y: usize, value: bool) {
        if self.is_x_y_valid(x, y) {
            self.matrix[x][y] = value
        } else {
            panic!("x and y are not valid");
        }
    }
}

impl Matrix {
    fn is_x_y_valid(&self, x: usize, y: usize) -> bool {
        x < WIDTH && y < HEIGHT
    }
}

impl<'a> IntoIterator for &'a Matrix {
    type Item = (usize, usize, bool);
    type IntoIter = MatrixIterator<'a>;

    fn into_iter(self) -> MatrixIterator<'a> {
        MatrixIterator {
            matrix: self,
            iter_x: 0,
            iter_y: 0,
        }
    }
}

pub struct MatrixIterator<'a> {
    matrix: &'a Matrix,
    iter_x: usize,
    iter_y: usize,
}

impl<'a> Iterator for MatrixIterator<'a> {
    type Item = (usize, usize, bool);

    fn next(&mut self) -> Option<Self::Item> {
        if self.matrix.is_x_y_valid(self.iter_x, self.iter_y) {
            let x = self.iter_x;
            let y = self.iter_y;

            self.iter_x += 1;
            if self.iter_x == WIDTH {
                self.iter_x = 0;
                self.iter_y += 1;
            }

            Some((x, y, self.matrix.get_value(x, y)))
        } else {
            None
        }
    }
}

fn display_matrix(matrix: &impl MatrixTrait) {
    println!("Matrix");
    for cell in matrix.into_iter() {
        print!("{}", if cell.2 { 'x' } else { '-' });
        if cell.0 == WIDTH - 1 {
            println!();
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: `Matrix` is not an iterator
  --> src/main.rs:32:6
   |
32 | impl MatrixTrait for Matrix {
   |      ^^^^^^^^^^^ `Matrix` is not an iterator
   |
   = help: the trait `std::iter::Iterator` is not implemented for `Matrix`
   = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `Matrix`

error: aborting due to previous error

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

To learn more, run the command again with --verbose.

You are trying to implement MatrixTrait for Matrix, but that requires that Matrix implements IntoIterator<Item = (usize, usize, bool)>, which you have only implemented for &Matrix. What you need is to either implement IntoIterator<Item = (usize, usize, bool)> for Matrix itself (which you have in your link), change the bound on MatrixTrait to say &Self, or change to implementing MatrixTrait for &Matrix. Based on your description, I think saying &Self instead of Self in the bound on MatrixTrait is what you want.

1 Like

Thank you for your help James :slightly_smiling_face:

Yes I want to keep the implementation of IntoIterator for &Matrix.

So I don't know if I understood your advice correctly, but this is what I did on my trait and it seems to work, but I'm wondering if I did that correctly...

trait MatrixTrait<'a>
where
    &'a Self: 'a + IntoIterator<Item = (usize, usize, bool)>,
{
    fn new() -> Self;
    fn get_value(&self, x: usize, y: usize) -> bool;
    fn set_value(&mut self, x: usize, y: usize, value: bool);
}

But now I struggle to use this trait in a function, because of the lifetime, I try many things but with no results:

fn display_matrix<'a>(matrix: &'a impl MatrixTrait) {
    println!("Matrix");
    for cell in matrix.into_iter() {
        print!("{}", if cell.2 { 'x' } else { '-' });
        if cell.0 == WIDTH - 1 {
            println!();
        }
    }
}

This is the error:

   Compiling playground v0.0.1 (/playground)
error[E0106]: missing lifetime specifier
   --> src/main.rs:102:40
    |
102 | fn display_matrix<'a>(matrix: &'a impl MatrixTrait) {
    |                                        ^^^^^^^^^^^ expected lifetime parameter

error: aborting due to previous error

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

To learn more, run the command again with --verbose.

The link to the updated playground:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5846ec411674b269b8c49dfe8ab02e84

You'll need to use HRTBs,

trait MatrixTrait
where
    //Read as "for any 'a, a reference to Self with lifetime 'a
    //implements ..."
    for<'a> &'a Self: IntoIterator<Item = (usize, usize, bool)>, 
    //No need for + 'a; &'a T already is 'a.
{
    fn new() -> Self;
    fn get_value(&self, x: usize, y: usize) -> bool;
    fn set_value(&mut self, x: usize, y: usize, value: bool);
}

But unfortunately your call site requirements become ugly because the compiler cannot infer that &'a Self: IntoIterator means that any T: MatrixTrait also must satisfy for<'a> &'a T: IntoIterator, so your call site must become this:

fn display_matrix<T: MatrixTrait>(matrix: &T) 
    where 
        for<'a> &'a T: IntoIterator<Item = (usize, usize, bool)> {
    println!("Matrix");
    for cell in matrix.into_iter() {
        print!("{}", if cell.2 { 'x' } else { '-' });
        if cell.0 == WIDTH - 1 {
            println!();
        }
    }
}

Playground

1 Like

Wow, Thanks a lot OptimisitcPeach! :smiley:

Would have been cool to not have to repeat that for the function but I understand the explanation, I will look to HRTBs :+1:

Just to be sure, there is no way to create a type alias to make this more clean?

I don't think you could solve this with a type alias; there is no support for trait aliases right now (I believe they're called existential types and have an rfc somewhere). Type bounds on type aliases aren't required to be satisfied,[Playground]. So, if you did manage to do something with a type alias, it'd still require you to have the where bound in your function, and would probably require more typing should you choose an appropriate name.

1 Like