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?
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>
.
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()));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With