Ok, you are definitely making progress.
What you are going to do is not the most straightforward thing, and it takes some time to get into it. I want to give you some suggestions, trying to explain you the problem and what you should do. At the end, I will give you a blurred solution, so you can make some practice and try to solve the problem by yourself. If you get stuck, you still can understand how to do that.
The main problem is related to Iterator
. What I mean is that Iterator
is a Trait, and you want to take any kind of struct that impl
-ements Iterator
. Maybe you already know that, but to fully understand the situation, you should make the function generic. In other words, you should write something like fn print_iter<Iter>(...)
. At that point, however, you have to bound that Iter
to the Iterator
trait.
Second point: are you sure that you are iterating over String
s? At the beginning, it is not so easy to fully understand the differences between String
, str
, CString
, CStr
and so on, so it is common to take the wrong type. Try to reason about ownership: which type of string owns the data and which one simply borrows a slice.
You will notice that the resolution of the second point brings you to another problem: lifetime. The best advice I can give you is: try to make the compiler figure it out by itself as much as you can.
Last point, less important but still a possible problem: remember that an Iterator
needs to be mut
-able to call next()
and provide you some elements. However, you are using a const ref to the iterator. Think about using a mut ref or passing the ownership.
Now, my suggestion is to try solving the problem by yourself. It can be a good learning exercise. I will leave you the solution below, with some useful comments.
The following is the most simple approach (but not so general):
fn print_iter<'a, Iter>(words: Iter)
where
Iter: Iterator<Item=&'a str>,
{
for w in words
{
println!("{}", w);
}
}
First of all, I am using a reference to a str
as an Item
type. This is because str
can be used to take slices of a not owned string (from String
or when you have 'static
lifetime). As you can see, it is necessary to introduce a lifetime 'a
in order to "make the compiler happy" (you are not specifying anything about it, but you have to mention it). And, obviously, the Iter
type is bound to Iterator
trait, in order to allow an iteration inside the function. Oh, you probably noticed that I decided to take the ownership of the iterator. In this case it is the best solution, because it is generally what you want. And in case you need the iterator again, you can just clone
it, it is a very cheap operation.
This solution could be also written as:
fn print_iter<'a>(words: impl Iterator<Item=&'a str>)
{
for w in words
{
println!("{}", w);
}
}
which is slightly more similar to your original implementation. The effect is very similar to the other notation (there is a difference about the usage of the turbofish notation, but nothing you have to be worried for now), you can choose which one you want to use.
Now, let's dive a bit deeper: I don't want an Iterator
that can strictly iterate through &str
-s, but I want everything that can be used as a str
. To do that, we can introduce another generic type and another bound:
fn print_iter<S, Iter>(words: Iter)
where
Iter: Iterator<Item=S>,
S: AsRef<str>,
{
for w in words
{
println!("{}", w.as_ref());
}
}
As you can see, now we take an iterator over item S
. What is S
? We don't know, but we can say that, if we call the as_ref
method on it, we will obtain a &str
. And that's what we are doing inside the function to print the values. It is worth noting that now I do not even need the lifetime 'a
anymore.
And to conclude, just a tiny stylish suggestion:
let sentence = "Rust is a very cool programming language with a lot of power.".to_owned();
This is simpler and easier to read. Obviously in this case you could have just used the &'static str
(removing to_owned
), but I think that this is part of your exercise, isn't it?