Returning different iterators of same type

I would like a function which returns one of two iterators, both iterate over &u32:
fn values(&self) -> impl Iterator<Item = &u32> + '_

However, I get an error:

error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:26:13
   |
23 | /         if self.use_table {
24 | |             self.table.iter().map(|it| &it.value)    
   | |             -------------------------------------
   | |             |                     |
   | |             |                     the expected closure
   | |             expected because of this
25 | |         } else {
26 | |             self.hash_table.values()
   | |             ^^^^^^^^^^^^^^^^^^^^^^^^ expected `Map<Iter<'_, KeyValue>, ...>`, found `Values<'_, usize, u32>`
27 | |         }
   | |_________- `if` and `else` have incompatible types

I tried boxing too, as suggested, but could not get it right.

Here is the full code:

	use std::collections::HashMap;

	struct KeyValue {
		key: usize,
		value: u32,
	}

	impl KeyValue {
		fn new(key: usize, value: u32) -> Self {
			Self { key, value }
		}
	}

	struct Data {
		table: Vec<KeyValue>,
		hash_table: HashMap<usize, u32>,
		use_table: bool,
	}

	impl Data {
		// actual goal, as fn values is a trait itself
                // both seem to have same return type
		fn values(&self) -> impl Iterator<Item = &u32> + '_ {
			if self.use_table {
				self.table.iter().map(|it| &it.value)
			} else {
				self.hash_table.values()
			}
		}

		// trying to box as help suggested
		fn values_2(&self) -> Box<impl Iterator<Item = &u32> + '_>{
			if self.use_table {
				Box::new(self.table_values()) 
			} else {
				Box::new(self.hash_table_values()) 
			}
		}

                // trying to return an iterator without reference and lifetime issues
		fn values_no_ref(&self) -> impl Iterator<Item = u32> {
			let x = self.table.iter().map(|&it| it.value);
			let y = self.hash_table.values().map(|it| *it);
			if self.use_table {
				x
			} else {
				y
			}
		}

		// same return type as above
		fn table_values(&self) -> impl Iterator<Item = &u32> + '_ {
			self.table.iter().map(|it| &it.value)
		}

		// same return type as above
		fn hash_table_values(&self) -> impl Iterator<Item = &u32> + '_ {
			self.hash_table.values()
		}
	}

	fn main() {
		let mut data = Data {
			table: Vec::new(),
			hash_table: HashMap::default(),
			use_table: false,
		};

		data.table = vec![
			KeyValue::new(1, 11),
			KeyValue::new(2, 22),
			KeyValue::new(3, 33),
		];
		data.hash_table.insert(4, 44);
		data.hash_table.insert(5, 55);
		data.hash_table.insert(6, 66);

		for value in data.values() {
			println!("value = {value}");
		}
	}

You need to return Box<dyn Iterator<..> + '_> not Box<impl Iterator<..>>.

		fn values(&self) -> Box<dyn Iterator<Item = &u32> + '_> {
			if self.use_table {
				Box::new(self.table.iter().map(|it| &it.value))
			} else {
				Box::new(self.hash_table.values())
			}
		}

-> impl Trait is an opaque but shallow type alias, which is why it has to resolve to the same type. dyn Trait + '_ is a type that represents some other, type-erased implementor of Trait (a base type) behind a pointer (like Box). So it can represent different base types.

2 Likes

Argh. I wrote Box<dyn impl...> instead of Box<dyn...>, got an error and thought it would not work.

1 Like

You could also use either::Either:

use either::Either;

impl Data {
    fn values(&self) -> impl Iterator<Item = &u32> {
        if self.use_table {
            Either::Left(self.table.iter().map(|it| &it.value))
        } else {
            Either::Right(self.hash_table.values())
        }
    }
}
4 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.