3

Because of reasons, I want to define a generic function that can iterate over key-value pairs expressed either as a mapping, or as a vector of 2-tuples (or anything else that satisfies IntoIterator<Item=(K, V)>, where K and V are stringy). Concretely, I want this to work:

use std::collections::HashMap; fn main() { let vc = vec![ ("a", "foo"), ("b", "bar"), ("c", "baz") ]; operate(&vc); let mut map = HashMap::new(); map.insert("d", "blurf"); map.insert("e", "quux"); map.insert("f", "xyzzy"); operate(&map); } 

I've got a definition of operate that works for the HashMap, but not for the vector:

fn operate<I, K, V>(x: I) where I: IntoIterator<Item=(K, V)>, K: AsRef<str>, V: AsRef<str> { for (ref k, ref v) in x { println!("{}: {}", k.as_ref(), v.as_ref()); } } 

The error message I get is

error[E0271]: type mismatch resolving `<&std::vec::Vec<(&str, &str)> as std::iter::IntoIterator>::Item == (_, _)` --> test.rs:18:5 | 18 | operate(&vc); | ^^^^^^^ expected reference, found tuple | = note: expected type `&(&str, &str)` = note: found type `(_, _)` = note: required by `operate` 

and I don't understand it at all. For one thing, it seems like it's backwards, and for another, why am I only getting an error for the Vec and not the HashMap?

2 Answers 2

7

The function provided by IntoIterator consumes self.

fn into_iter(self) -> Self::IntoIter 

In order to allow the use of IntoIterator without consuming the collection, both Vec and HashMap have implementations of IntoIterator for &'a Vec<T> and &'a HashMap<K,V,S>, respectively. However, they are not quite the same.

For the hash map, each Item is a (&K, &V), which does not impose a problem because the code effectively assumes the items as 2-sized tuples of keys and values that coerce to &str. And &&str does indeed coerce to &str. For the vector, each Item is a &T (thus &(K, V) in this case), but because the function is expecting (K, V) as the iterating item, it is currently unable to deal with items of &(K, V).

As it is, the function works if you move the vector, which yields an IntoIterator where Item = (K, V):

let vc = vec![ ("a", "foo"), ("b", "bar"), ("c", "baz") ]; operate(vc); 

But what if we want it to work for both collections without consuming any of them? Well, I just devised two solutions.

#1

This one involves hiding the tuple behind a new trait:

/// for stuff that can be turned into a pair of references trait AsRefPair<K, V> { fn as_ref_pair(&self) -> (&K, &V); } 

Implementing it for for &(K,V) and (&K,&V):

impl<'a, K, V> AsRefPair<K, V> for (&'a K, &'a V) { fn as_ref_pair(&self) -> (&K, &V) { (self.0, self.1) } } impl<'a, K, V> AsRefPair<K, V> for &'a (K, V) { fn as_ref_pair(&self) -> (&K, &V) { (&self.0, &self.1) } } 

And now this function works:

fn operate<I, T, K, V>(x: I) where I: IntoIterator<Item=T>, T: AsRefPair<K, V>, K: AsRef<str>, V: AsRef<str> { for p in x { let (ref k, ref v) = p.as_ref_pair(); println!("{}: {}", k.as_ref(), v.as_ref()); } } 

Playground. It might sound a bit crazy at first, but...!

#2

In this one, just stop working with tuples... and start working with key-values!

trait KeyValue<K, V> { fn key_value(&self) -> (&K, &V) { (self.key(), self.value()) } fn key(&self) -> &K; fn value(&self) -> &V; } impl<K, V> KeyValue<K, V> for (K, V) { fn key(&self) -> &K { &self.0 } fn value(&self) -> &V { &self.1 } } impl<'a, K, V> KeyValue<K, V> for &'a (K, V) { fn key(&self) -> &K { &self.0 } fn value(&self) -> &V { &self.1 } } fn operate<I, T, K, V>(x: I) where I: IntoIterator<Item=T>, T: KeyValue<K, V>, K: AsRef<str>, V: AsRef<str> { for p in x { let (ref k, ref v) = p.key_value(); println!("{}: {}", k.as_ref(), v.as_ref()); } } 

Playground. I find this one a bit more idiomatic.

Sign up to request clarification or add additional context in comments.

2 Comments

It's required in context (see the hyperlink in the question) that operate not consume its argument. Any other options? Some way to write the match pattern in the for so it works either way, for instance?
I was working on a solution, hang in there! :) Done!
2

If you pass to the function operate() an iterator instead of a reference to vector, you can use Iterator adaptors to convert Iterator::Item to what you need:

operate(vc.iter().map(|&(ref a, ref b)| (a, b))); 

2 Comments

Indeed, using iterators is probably the most idiomatic way of doing this as it gives a lot more flexibility (also allows filtering the input, for example).
I believe Iterator implements IntoIterator trivially, so taking IntoIterator seems more general.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.