Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does cloned() allow this function to compile

Tags:

rust

I'm starting to learn Rust and I tried to implement a function to reverse a vector of strings. I found a solution but I don't understand why it works.

This works:

fn reverse_strings(strings:Vec<&str>) -> Vec<&str> {
  let actual: Vec<_> = strings.iter().cloned().rev().collect();
  return actual;
}

But this doesn't.

fn reverse_strings(strings:Vec<&str>) -> Vec<&str> {
  let actual: Vec<_> = strings.iter().rev().collect(); // without clone
  return actual;
}

Error message

src/main.rs:28:10: 28:16 error: mismatched types:
 expected `collections::vec::Vec<&str>`,
   found `collections::vec::Vec<&&str>`
(expected str,
    found &-ptr) [E0308]

Can someone explain to me why? What happens in the second function? Thanks!

like image 688
fast_cen Avatar asked Jul 04 '15 06:07

fast_cen


1 Answers

So the call to .cloned() is essentially like doing .map(|i| i.clone()) in the same position (i.e. you can replace the former with the latter).

The thing is that when you call iter(), you're iterating/operating on references to the items being iterated. Notice that the vector already consists of 'references', specifically string slices.

So to zoom in a bit, let's replace cloned() with the equivalent map() that I mentioned above, for pedagogical purposes, since they are equivalent. This is what it actually looks like:

.map(|i: & &str| i.clone())

So notice that that's a reference to a reference (slice), because like I said, iter() operates on references to the items, not the items themselves. So since a single element in the vector being iterated is of type &str, then we're actually getting a reference to that, i.e. & &str. By calling clone() on each of these items, we go from a & &str to a &str, just like calling .clone() on a &i64 would result in an i64.

So to bring everything together, iter() iterates over references to the elements. So if you create a new vector from the collected items yielded by the iterator you construct (which you constructed by calling iter()) you would get a vector of references to references, that is:

let actual: Vec<& &str> = strings.iter().rev().collect();

So first of all realize that this is not the same as the type you're saying the function returns, Vec<&str>. More fundamentally, however, the lifetimes of these references would be local to the function, so even if you changed the return type to Vec<& &str> you would get a lifetime error.

Something else you could do, however, is to use the into_iter() method. This method actually does iterate over each element, not a reference to it. However, this means that the elements are moved from the original iterator/container. This is only possible in your situation because you're passing the vector by value, so you're allowed to move elements out of it.

fn reverse_strings(strings:Vec<&str>) -> Vec<&str> {
  let actual: Vec<_> = strings.into_iter().rev().collect();
  return actual;
}

playpen

This probably makes a bit more sense than cloning, since we are passed the vector by value, we're allowed to do anything with the elements, including moving them to a different location (in this case the new, reversed vector). And even if we don't, the vector will be dropped at the end of that function anyways, so we might as well. Cloning would be more appropriate if we're not allowed to do that (e.g. if we were passed the vector by reference, or a slice instead of a vector more likely).

like image 51
Jorge Israel Peña Avatar answered Oct 22 '22 15:10

Jorge Israel Peña