Search code examples
rustiterator

Function to accept either Vec or HashSet to provide iter


fn foo<T>(t: T) {
    // do stuff
    t.iter() ...
}

I have this function where the concrete types passed are either a Vec<(String, String)> or a HashSet<(String, String)>.

Inside it I want to iterate over the collection twice without cloning or first collecting to a Vec. Both types have the iter() method available which doesn't consume, but no trait exists to bound the generic type by to allow me to access it.

Is there a better way than to define a custom ad hoc trait Iter for both Vec and HashSet?


Solution

  • I'd use IntoIterator as a trait bound on T to accomplish what you want. IntoIterator is implemented for both &'a Vec<T> and &'a HashSet<T>, so you can pass a reference to your vector or hash set to foo without consuming either, enabling you to iterate over the values as often as you like:

    use std::collections::HashSet;
    
    fn foo<'a, T>(t: T)
    where
        T: IntoIterator<Item = &'a (String, String)> + Copy,
    {
        for (k, v) in t {
            println!("{k}: {v}");
        }
    
        for (k, v) in t {
            println!("{k}: {v}");
        }
    }
    
    fn main() {
        let map: HashSet<(String, String)> = HashSet::from_iter([("foo".to_owned(), "bar".to_owned())]);
        let vec = vec![("foo".to_owned(), "bar".to_owned())];
    
        foo(&map);
        foo(&vec);
    }
    

    stdout:

    foo: bar
    foo: bar
    foo: bar
    foo: bar
    

    Playground.

    Note that we need the Copy trait bound on T to tell static analysis that we are not moving out of t in our first loop. This bound is no problem though, because references are always Copy, regardless of the referent.