Flatten a vector of structures containing a vector

Hi all,

Let have these structs:

type Member = String;

struct Album {
   year: u16,
   band_members: Vec<Member>
}

struct BestOf(Vec<Album>);

How to define an iterator on BestOf for all members using the IntoIterator trait ?

// this obviously doesn't compile
impl<'a> IntoIterator for &'a BestOf {
    type Item = &'a Member;
    type IntoIter = std::slice::Iter<'a, Member>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter().flatten().iter()
    }
}

Thanks a lot for your help.

First of all, self.0.iter().flatten().iter() is wrong by itself; it wouldn't work even outside of a IntoIterator implementation. You would need:

self.0.iter().flat_map(|album| &album.band_members)

But then, the problem is that it's difficult to write the type of this chain of iteration adapters. It's possible in this case, but inelegant:

impl<'a> IntoIterator for &'a BestOf {
    type Item = &'a Member;
    type IntoIter =
        iter::FlatMap<slice::Iter<'a, Album>, &'a Vec<Member>, fn(&'a Album) -> &'a Vec<Member>>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter().flat_map(|album| &album.band_members)
    }
}

The general solution is to write your own struct that has an impl Iterator, and use that instead of using iterator combinators like flat_map(). That's tedious but currently necessary. (If you look at other people's IntoIterator implementations, you'll see they almost always do this.)

In the future, we may be able to use impl Iterator opaque types to say just "there's some kind of iterator here", but that feature isn't yet in stable Rust.

2 Likes

@kpreid Thanks for your swift answer ! That's what I feared :wink:

You could use a trait object to make the IntoIter definition slightly more elegant, but it'd require you to wrap it in a Box:

type Member = String;

struct Album {
   year: u16,
   band_members: Vec<Member>
}

struct BestOf(Vec<Album>);

impl<'a> IntoIterator for &'a BestOf {
    type Item = &'a Member;
    type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;
    
    fn into_iter(self) -> Self::IntoIter {
        Box::new(self.0.iter().flat_map(|a| &a.band_members))
    }
}

Playground.

@jofas Thanks for your suggestion !

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.