Christoph
Christoph

Reputation: 27985

How do I use the Iterator trait to build generic APIs

I may not see the forest for the trees but I wonder how do I actually design my methods to not work against hard collection types but against Iterators instead. Consider this method.

pub fn print_strings(strings: Vec<String>) {
    for val in strings.iter() {
        println!("{}", val);
    }
}

Obviously this falls short if I want to use that with an HashSet or HashMap.

So, I tried this:

use std::collections::*;

fn main () {
    let strings = vec!("Foo", "Bar");

    let mut more_strings = HashMap::new();
    more_strings.insert("foo", "bar");
    more_strings.insert("bar", "foo");

    print_strings(&strings.iter());
    print_strings(&more_strings.values())
}

fn print_strings(strings: &Iterator<Item=&str>) {
    for val in strings {
        println!("{}", val);
    }
}

Playpen (also to view lengthy compiler error)

http://is.gd/EYIK11

Unfortunately, this doesn't seem to do the trick either. What am I missing?

Upvotes: 6

Views: 138

Answers (3)

Veedrac
Veedrac

Reputation: 60127

Even better, you can do

fn print_strings<Iterable>(strings: Iterable)
    where Iterable: IntoIterator,
          Iterable::Item: AsRef<str>
{
    for val in strings {
        println!("{}", val.as_ref());
    }
}

(Kudos Shepmaster for the improvement.)

This means that you can call this with &mut Iterators for dynamic dispatch or concrete iterator or collection types for static dispatch. Further, the iterator type can be anything that can be simply converted to &str, which includes but is not limited to &str, &&str and even String.

print_strings(&strings);
print_strings(strings.iter().map(|s| s.to_owned()));
print_strings(vec![&&&&"xyz"]);
print_strings(strings);
print_strings(more_strings.values());

Upvotes: 7

defyrlt
defyrlt

Reputation: 136

playpen
So the first thing is that you're expecting Iterator<Item=&str>, but it's Iterator<Item=&&str> actually.
Then, you're trying to call .iter(), but Iterator doesn't have this method. You can simply remove .iter() call and receive (and send ofc) &mut Iterator<...> in order to get for loop working (for loop needs something, that implements IntoIterator, and &mut Iterator is that thing).
Add lifetimes and you're all set! :)
Also, I'm recommending to use static dispatch. You can see it in the example I provided.

Upvotes: 4

Adrian
Adrian

Reputation: 15171

When you call .iter() on a Vec<T>, you get an Iterator<Item=&T>. So when you call .iter() on a Vec<&str>, you get an Iterator<Item=&&str>, not a Iterator<Item=&str>. You should look at the .cloned() method for Iterator, it should help solve your problem.

Also, note that in order to iterate through an iterator, you must be able to mutate it (either own the iterator or have a mutable reference to it). So just having an immutable reference to it is sorta useless. I would recommend moving the iterator value into print_strings rather than passing it by reference. If you want to use trait objects for this, you can do that by using Box, but it might be easier to just make print_strings a generic function.

Upvotes: 4

Related Questions