What does the ' mean in Split<'a, P>?

I have a function which returns an iterator (.split()) that I'm trying to assign to a variable. I need to pass that variable into another function but I'm having difficulty with the receiving function's signature because I do not know how to specify to Rust the proper type in the function signature. I looked up in the docs what std::String.split() returns and it returns Split<'a,P>

I kind of understand the generic syntax but the ' is thorwing me off. Can someone explain to me what it means and how I could properly pass an iterator into another function? Thank you.

It's a lifetime. You should read the book to learn the basic syntax. As for iterators, they're passed like anything else; they're not special.

2 Likes

Assuming you are referring to the String::split():

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P> where
    P: Pattern<'a>

The function takes a generic lifetime 'a, and a generic type P which must implement Pattern<'a>. These show up between the brackets after the function name (split<'a, P>).

The same 'a is being used in three places:

  1. The &'a self argument. This is a borrow that must live as long as 'a (can live longer).
  2. The Split<'a, P> return type. This specifies that whatever this function returns is a Split type with the provided lifetime 'a and generic type P. Internally the Split type might be using 'a as a lifetime in many ways.
  3. The Pattern<'a> in the where clause. This means that whatever the type P is. It must implement the Pattern trait with 'a as the generic lifetime.

The reason Split takes a specific lifetime is that it's tied to some Pattern, and the reason Pattern takes a specific lifetime is because it's tied to some reference to a string &'a str. Therefore the whole thing depends on the lifetime of the string 'a.

3 Likes

Ok thanks to your information, I've definitely learned a lot more about Traits and Lifetimes in Rust today.

I'm still not connecting the dots on the iterator pass though so let me show you a small program demonstrating what I'm trying to do:

fn main()
{
    let sentence = std::string::String::from("Rust is a very cool programming langauge with a lot of power.");
    let words = sentence.split(" "); // Should be an iterable object now
}

fn print_iter(words: &Iterator<Item=String>)
{
    for w in words
    {
        println!("{}\n", w);
    }
}

Does this make sense? I'm trying to pass the iterable object returned from a call to .split() to another func.

Errors I get:

When trying to do the above:

error[E0277]: the trait bound `&dyn std::iter::Iterator<Item=std::string::String>: std::iter::Iterator` is not satisfied
 --> iter_test.rs:9:14
  |
9 |     for w in words
  |              ^^^^^ `&dyn std::iter::Iterator<Item=std::string::String>` is not an iterator; maybe try calling `.iter()` or a similar method
  |
  = help: the trait `std::iter::Iterator` is not implemented for `&dyn std::iter::Iterator<Item=std::string::String>`
  = note: required by `std::iter::IntoIterator::into_iter`

error: aborting due to previous error

When I do that I get:

error[E0599]: no method named `iter` found for type `&dyn std::iter::Iterator<Item=std::string::String>` in the current scope
 --> iter_test.rs:9:20
  |
9 |     for w in words.iter()
  |                    ^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0599`.

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 Strings? 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?

2 Likes

Both solutions above consume the iterator. This is probably what you want, since the for loop exhausts all iterable items anyway. There are some cases where iterators can continue producing new results, and in those cases, consuming the iterator would be unwanted.

For those special cases, you can pass a mutable borrow to the print_iter function. This will allow you to continue using the iterator after the function returns.

Geez, you guys are so high-tech around here you even have "blurred solutions!" First I've seen that haha.

Anyway, thanks a lot for that, I haven't worked through your solution yet because in the mean time, I came up with a workaround solution that I am currently using. However, as soon as I am finished with the first iteration of this little app I'm going to then do your method and I will also show a version of what I did here for others to learn how they are different. My temporary solution involved collecting the iterator whereas I believe yours probably answers the question as I asked which is great and I still need to learn that. In the meantime, I listened to 3 rust podcasts, a couple of which covered traits that really helped. Additionally, I'm getting into multithreading now. Ofc this is all in an effort to get a specific app done in a native language... Fast-track Rust if you will :smiley:

I'll keep you updated!

1 Like

@dodomorandi alright so as promised, I decided not to show the vector workaround I came up with because it doesn't solve the problem I asked. Instead, I just spent some time working on it myself and this is what I came up with based on compiler error hints... Looks a bit different from what you did but it does work:

fn main()
{
    let sentence = std::string::String::from("Rust is a very cool programming langauge with a lot of power.");
    let mut words = sentence.split(" "); // Should be an iterable object now
    print_iter(&mut words);
}

fn print_iter(words: &mut std::str::Split<'_, &str>)
{
    for w in words
    {
        println!("{}\n", w);
    }
}

Ok!

Can I suggest you a further exercise, in order to improve the example? Try to pass to print_iter an iterator of the words with a prime 1-based index.