Why doesn't FilterMap convert to proper Iterator?

in this struct, find1 returns Vec<&Rec> and works correctly. Trying to return impl Iterator<Item = &Rec>, I get errors. Compiler thinks I return FilterMap<..., &Key> while I definitely convert them into &Rec.

Adding into_iter() or into() won't fix this.

How to work around this without creating vecs?

struct Category(usize);
#[derive(Hash, PartialEq, Eq)]
struct Key(u32);
struct Rec(i64);

struct MyStruct {
	index: HashMap<Category, HashSet<Key>>,
	data: HashMap<Key, Rec>
}

impl MyStruct {
	fn find1(&self, cat: &Category) -> Vec<&Rec> {
		let Some(keys) = self.index.get(cat) else { return vec![] };
		keys.iter().filter_map(|k| self.data.get(k)).collect()
	}

	fn find2(&self, cat: &Category) -> impl Iterator<Item = &Rec> {
		let Some(keys) = self.index.get(cat) else { return vec![].iter() };
        keys.iter().filter_map(|k| self.data.get(k))
	}
}

Error output:

error[E0308]: mismatched types
  --> src/utils/table.rs:25:3
   |
23 |     fn find2(&self, cat: &Category) -> impl Iterator<Item = &Rec> {
   |                                        -------------------------- expected `std::slice::Iter<'_, Rec>` because of return type
24 |         let Some(keys) = self.index.get(cat) else { return vec![].iter() };
25 |         keys.iter().filter_map(|k| self.data.get(k))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `Iter`, found struct `FilterMap`
   |
   = note: expected struct `std::slice::Iter<'_, Rec>`
              found struct `FilterMap<std::collections::hash_set::Iter<'_, Key>, [closure@src/utils/table.rs:25:26: 25:29]>`

impl Trait only ever stands for a single concrete type within the same function. You cannot both return a slice::Iter and a FilterMap.

A workaround.

3 Likes

Generally, you need a way to unify both iterators into a single type. You can always do this by turning them into boxed trait objects, but another way that works almost as much is to return Either.

pub fn find3(&self, cat: &Category) -> impl Iterator<Item = &Rec> {
	let Some(keys) = self.index.get(cat) else { return Either::Left(std::iter::empty()) };
    let right = keys.iter().filter_map(|k| self.data.get(k));
    Either::Right(right)
}

But in this case, you have an Option, which already has the behavior you need.

fn find4(&self, cat: &Category) -> impl Iterator<Item = &Rec> {
	self.index.get(cat).into_iter().flatten().filter_map(|k| self.data.get(k))
}
4 Likes

Oh, I see. I misinterpreted the error message, thought it meant that FlatMap couldn't be converted into iterator, while it was just a conflict of two iterators. Thanks to both you guys!

2 Likes

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.