Returning Iterators in Traits

Hi All,

I'm having a row with the compiler and hoping you can set me straight!

The concept is that I have some images which are re-arranged by row. I have sources where the original data makes sense to be owned but I would like the option of it to be borrowed from the original source to avoid a copy if it will immediately be copied to the final image anyway.

Great I thought! I can have a trait for a data reader which allows me to fetch an iterator of the rows where each row is represented as a slice.

I've struggled with lifetimes but seem to have appeased the compiler on that (but feels like I've just done whats asked so let me know if there appears to be a simpler way!)

However I'm struggling with returning a valid Iterator object. The owned items iteration is generated by chunks of the stored vector. The borrowed items is an iterator over a vector. I thought I can use -> impl Iterator<Item=T> but that complains because it can't be a return in a trait?

So I found another thread that pointed me to the solution below. Creating a generic type parameter on the impl block and making this the iterator trait. (I'm also aware of Box but performance is very important so I avoided this)

However this still fails to compile.

What am I missing? How can I get this to work and/or am I missing a better design? (I often find this is the case when fighting the compiler)


pub trait ChipRowReader<'a, T: 'a, I: Iterator<Item=&'a&'a[T]>> {
    fn row_iter(&self) -> I;
}

struct BorrowedChipData<'a,T: Copy> {
    data: Vec<&'a[T]>
}

impl<'a, T:Copy> BorrowedChipData<'a,T> {
    fn new(rows: usize, columns: usize) -> Self {
        return Self{
            data: Vec::with_capacity(rows)
        };
    }

    fn push_row(&mut self, row: &'a [T]) {
        self.data.push(row);
    }


}


impl<'a, T: Copy, I: Iterator<Item=&'a&'a[T]>> ChipRowReader<'a, T, I> for BorrowedChipData<'a, T> {

    fn row_iter(&self) -> I {
        self.data.iter()
    }
}
struct OwnedChipData<T: Copy> {
    columns: usize,
    data: Vec<T>
}

impl<T: Copy> OwnedChipData<T> {
    fn new(rows: usize, columns: usize) -> Self {
        return Self{
            columns: columns,
            data: Vec::with_capacity(rows * columns)
        };
    }

    fn push_row(&mut self, row: & [T]) {
        self.data.extend_from_slice(row);
    }
}

impl<'a, T: 'a + Copy, I: Iterator<Item=&'a&'a[T]>> ChipRowReader<'a, T, I> for OwnedChipData<T> {


    fn row_iter(& self) -> I {
        let data = self.data;

        &data[..].chunks(self.columns)
    }

}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/lib.rs:28:9
   |
25 | impl<'a, T: Copy, I: Iterator<Item=&'a&'a[T]>> ChipRowReader<'a, T, I> for BorrowedChipData<'a, T> {
   |                   - this type parameter
26 | 
27 |     fn row_iter(&self) -> I {
   |                           - expected `I` because of return type
28 |         self.data.iter()
   |         ^^^^^^^^^^^^^^^^ expected type parameter `I`, found struct `std::slice::Iter`
   |
   = note: expected type parameter `I`
                      found struct `std::slice::Iter<'_, &[T]>`

error[E0308]: mismatched types
  --> src/lib.rs:55:9
   |
49 | impl<'a, T: 'a + Copy, I: Iterator<Item=&'a&'a[T]>> ChipRowReader<'a, T, I> for OwnedChipData<T> {
   |                        - this type parameter
...
52 |     fn row_iter(& self) -> I {
   |                            - expected `I` because of return type
...
55 |         &data[..].chunks(self.columns)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `I`, found `&Chunks<'_, T>`
   |
   = note: expected type parameter `I`
                   found reference `&Chunks<'_, T>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to 2 previous errors

What you are missing here is that the Iterator should probably be an associated type rather than a generic parameter to the trait. A generic parameter means there can be multiple implementations of ChipRowReader and the caller of row_iter() gets to pick which is used. As is currently written you are trying to implement it for every possible Iterator, which doesn't work since the bodies of row_iter() each return a specific type.

An associated type instead associates a single type with each implementation of the trait, so the types OwnedChipData and BorrowedChipData would determine the type of the Iterator (Iter or Chunks). A definition of the trait with an associated type would be:

pub trait ChipRowReader<'a, T: 'a> {
    type Iter : Iterator<Item=&'a&'a[T]>;
    
    fn row_iter(&self) -> Self::Iter;
}

Here's a playground example that compiles. I changed the Item type to &'a [T] and wrapped the std::slice::Iter in a copied() so that it wouldn't have a nested reference (which makes the Item type consistent with that of Chunks).

This looks great thank you.

Looks like I'm still getting my head around traits vs traditional inheritance but your explanation makes sense to me.

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.