Writing a funtion to take an iterator of strings

I'm trying to write a function that takes multiple strings and processes them. For simplicity, let's just say I want to print them. The basic function is easy:

fn out(strings) {
    for s in strings {
        println!("{}", s);
    }
}

The problem I have here is getting the type for strings correct. I could just put : u32 in and let the compiler tell me, but my problem is that I want to keep things general. In particular, I would like to be able to write:

let data = vec!["one", "two"];
out(data);

for testing, but

out(std::env::args().skip(1)

for the real code.

And I don't want to hard code something as specific as std::iter::Skip<std::env::Args>... Also, I want to keep things reasonably efficient - so I'd like to avoid generating multiple copies of the code if I don't need to, and I'd like to avoid copying the strings to the heap if I don't have to. These aren't hard and fast requirements - for my actual code, I could just make a Vec<String> and stop obsessing, but it bugs me to do that just because I don't know how to do it more generically. Once I do know what the generic solution looks like, I may decide it's more complex than I need in this case, and go with the simple solution, of course :slightly_smiling_face:

I think what I need is a generic, something like

fn out<I>(strings: I) -> ()
where I: Iterator<Item=&str>
{
    /* Same code as before */
}

But that doesn't work:

   Compiling playground v0.0.1 (/playground)
error[E0637]: `&` without an explicit lifetime name cannot be used here
 --> src/main.rs:5:24
  |
5 | where I: Iterator<Item=&str>
  |                        ^ explicit lifetime name needed here

error[E0277]: `std::vec::Vec<&str>` is not an iterator
  --> src/main.rs:14:9
   |
4  | fn out<I>(strings: I) -> ()
   |    ---
5  | where I: Iterator<Item=&str>
   |          ------------------- required by this bound in `out`
...
14 |     out(data);
   |         ^^^^ `std::vec::Vec<&str>` is not an iterator
   |
   = help: the trait `std::iter::Iterator` is not implemented for `std::vec::Vec<&str>`

error[E0271]: type mismatch resolving `<std::iter::Skip<std::env::Args> as std::iter::Iterator>::Item == &'static str`
  --> src/main.rs:15:5
   |
4  | fn out<I>(strings: I) -> ()
   |    ---
5  | where I: Iterator<Item=&str>
   |                   --------- required by this bound in `out`
...
15 |     out(env::args().skip(1));
   |     ^^^ expected `&str`, found struct `std::string::String`

I think what I need is to be able to say "something that can be converted to an Iterator whose item type can be converted to a &str`. But I don't know how to do that, and I don't know what terms I should be searching for.

Can anyone give me any pointers - to either the correct approach, or to some search terms I can use to look for hints?

1 Like

The IntoIterator trait exists for the first purpose, and AsRef<str> can be used for the latter:

fn out<I>(strings: I)
where
    I: IntoIterator,
    I::Item: AsRef<str>,
{
    for s in strings {
        println!("{}", s.as_ref());
    }
}

Note that the lifetime errors you ran into can be avoided like this:

fn out<'a, I>(strings: I) -> ()
where
    I: IntoIterator<Item = &'a str>,
{
    for s in strings {
        println!("{}", s);
    }
}

Be aware that the difference between the two ways to specify what the item should be are because in one case I'm just saying the item should implement a trait, but in the second case I'm explicitly saying it should be a specific type.

3 Likes

Awesome! Thanks for that, it's exactly what I was looking for. I recall reading about these, but like with a lot of things, reading about features in isolation, doesn't "stick" and when I come to actually need them, I don't remember the specifics. And the concepts are all new, so I find there's not much in the way of obvious terms I can use to google for answers.