nevenoomo
nevenoomo

Reputation: 23

How to implement an iterator over chunks of an array in a struct?

I want to implement an iterator for the struct with an array as one of its fields. The iterator should return a slice of that array, but this requires a lifetime parameter. Where should that parameter go?

The Rust version is 1.37.0

struct A {
    a: [u8; 100],
    num: usize,
}

impl Iterator for A {
    type Item = &[u8]; // this requires a lifetime parameter, but there is none declared

    fn next(&mut self) -> Option<Self::Item> {
         if self.num >= 10 {
             return None;
         }

         let res = &self.a[10*self.num..10*(self.num+1)];
         self.num += 1;
         Some(res)
    }
}

Upvotes: 2

Views: 2393

Answers (2)

Shepmaster
Shepmaster

Reputation: 431809

I wouldn't implement my own. Instead, I'd reuse the existing chunks iterator and implement IntoIterator for a reference to the type:

struct A {
    a: [u8; 100],
    num: usize,
}

impl<'a> IntoIterator for &'a A {
    type Item = &'a [u8];
    type IntoIter = std::slice::Chunks<'a, u8>;

    fn into_iter(self) -> Self::IntoIter {
        self.a.chunks(self.num)
    }
}
fn example(a: A) {
    for chunk in &a {
        println!("{}", chunk.iter().sum::<u8>())
    }
}

Upvotes: 2

SCappella
SCappella

Reputation: 10474

When you return a reference from a function, its lifetime needs to be tied to something else. Otherwise, the compiler wouldn't know how long the reference is valid (the exception to this is a 'static lifetime, which lasts for the duration of the whole program).

So we need an existing reference to the slices. One standard way to do this is to tie the reference to the iterator itself. For example,

struct Iter<'a> {
    slice: &'a [u8; 100],
    num: usize,
}

Then what you have works almost verbatim. (I've changed the names of the types and fields to be a little more informative).

impl<'a> Iterator for Iter<'a> {
    type Item = &'a [u8];

    fn next(&mut self) -> Option<Self::Item> {
        if self.num >= 100 {
            return None;
        }

        let res = &self.slice[10 * self.num..10 * (self.num + 1)];
        self.num += 1;
        Some(res)
    }
}

Now, you probably still have an actual [u8; 100] somewhere, not just a reference. If you still want to work with that, what you'll want is a separate struct that has a method to convert into A. For example

struct Data {
    array: [u8; 100],
}

impl Data {
    fn iter<'a>(&'a self) -> Iter<'a> {
        Iter {
            slice: &self.array,
            num: 0,
        }
    }
}

Thanks to lifetime elision, the lifetimes on iter can be left out:

impl Data {
    fn iter(&self) -> Iter {
        Iter {
            slice: &self.array,
            num: 0,
        }
    }
}

(playground)

Just a few notes. There was one compiler error with [0u8; 100]. This may have been a typo for [u8; 100], but just in case, here's why we can't do that. In the fields for a struct definition, only the types are specified. There aren't default values for the fields or anything like that. If you're trying to have a default for the struct, consider using the Default trait.

Second, you're probably aware of this, but there's already an implementation of a chunk iterator for slices. If slice is a slice (or can be deref coerced into a slice - vectors and arrays are prime examples), then slice.chunks(n) is an iterator over chunks of that slice with length n. I gave an example of this in the code linked above. Interestingly, that implementation uses a very similar idea: slice.chunks(n) returns a new struct with a lifetime parameter and implements Iterator. This is almost exactly the same as our Data::iter.

Finally, your implementation of next has a bug in it that causes an out-of-bounds panic when run. See if you can spot it!

Upvotes: 1

Related Questions