Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper signature for a function accepting an iterator of strings

Tags:

rust

I'm confused about the proper type to use for an iterator yielding string slices.

fn print_strings<'a>(seq: impl IntoIterator<Item = &'a str>) {
    for s in seq {
        println!("- {}", s);
    }
}

fn main() {
    let arr: [&str; 3] = ["a", "b", "c"];
    let vec: Vec<&str> = vec!["a", "b", "c"];
    let it: std::str::Split<'_, char> = "a b c".split(' ');

    print_strings(&arr);
    print_strings(&vec);
    print_strings(it);
}

Using <Item = &'a str>, the arr and vec calls don't compile. If, instead, I use <Item = &'a'a str>, they work, but the it call doesn't compile.

Of course, I can make the Item type generic too, and do

fn print_strings<'a, I: std::fmt::Display>(seq: impl IntoIterator<Item = I>)

but it's getting silly. Surely there must be a single canonical "iterator of string values" type?

like image 952
Søren Løvborg Avatar asked Jan 24 '23 08:01

Søren Løvborg


2 Answers

The error you are seeing is expected because seq is &Vec<&str> and &Vec<T> implements IntoIterator with Item=&T, so with your code, you end up with Item=&&str where you are expecting it to be Item=&str in all cases.

The correct way to do this is to expand Item type so that is can handle both &str and &&str. You can do this by using more generics, e.g.

fn print_strings(seq: impl IntoIterator<Item = impl AsRef<str>>) {
    for s in seq {
        let s = s.as_ref();
        println!("- {}", s);
    }
}

This requires the Item to be something that you can retrieve a &str from, and then in your loop .as_ref() will return the &str you are looking for.

This also has the added bonus that your code will also work with Vec<String> and any other type that implements AsRef<str>.

like image 114
loganfsmyth Avatar answered Mar 30 '23 00:03

loganfsmyth


TL;DR The signature you use is fine, it's the callers that are providing iterators with wrong Item - but can be easily fixed.

As explained in the other answer, print_string() doesn't accept &arr and &vec because IntoIterator for &[T; n] and &Vec<T> yield references to T. This is because &Vec, itself a reference, is not allowed to consume the Vec in order to move T values out of it. What it can do is hand out references to T items sitting inside the Vec, i.e. items of type &T. In the case of your callers that don't compile, the containers contain &str, so their iterators hand out &&str.

Other than making print_string() more generic, another way to fix the issue is to call it correctly to begin with. For example, these all compile:

print_strings(arr.iter().map(|sref| *sref));
print_strings(vec.iter().copied());
print_strings(it);

Playground

iter() is the method provided by slices (and therefore available on arrays and Vec) that iterates over references to elements, just like IntoIterator of &Vec. We call it explicitly to be able to call map() to convert &&str to &str the obvious way - by using the * operator to dereference the &&str. The copied() iterator adapter is another way of expressing the same, possibly a bit less cryptic than map(|x| *x). (There is also cloned(), equivalent to map(|x| x.clone()).)

It's also possible to call print_strings() if you have a container with String values:

let v = vec!["foo".to_owned(), "bar".to_owned()];
print_strings(v.iter().map(|s| s.as_str()));
like image 28
user4815162342 Avatar answered Mar 30 '23 01:03

user4815162342