1

I have a function that takes char_vec: &Vec<Chars> as an argument. I know that the length of each Chars may be different - I want to get the length of the longest one. I'm trying to do it with a following code:

let longest = char_vec.iter().map(|x| x.count()).max().unwrap_or(0);

Unfortunately this fails with the following error:

error[E0507]: cannot move out of `*x` which is behind a shared reference
  --> C:\Rust\playground\src\lib.rs:62:43
   |
62 |     let longest = char_vec.iter().map(|x| x.count()).max().unwrap_or(0);
   |                                           ^ move occurs because `*x` has type `std::str::Chars<'_>`, which does not implement the `Copy` trait

I don't really understand how to execute the count() method here. The Chars struct seems to not implement a common traits?

Djent
  • 2,877
  • 10
  • 41
  • 66

3 Answers3

2

The Chars do implement Iterator trait (and so it can use its methods). The problem here that count accepts self by-value, i.e. has signature Iterator::count(self) -> usize. On the other side, iter you've called iterates over references, i.e. over &Chars rather than Chars. So you need to convert an item to the owned one, i.e.:

use std::iter::Iterator;
let longest = v.iter().cloned().map(Iterator::count).max().unwrap_or(0);

// you may also do the same with each item explicitly:
let longest = v.iter().map(|x| x.clone().count()).max().unwrap_or(0);

Keep in mind that cloning there is a fairly cheap operation (as well as with the most of the other iterators). Chars iterator is just a pair of pointers. So the copying performed on this pair of integer values rather than a whole underlying string.

Kitsu
  • 3,166
  • 14
  • 28
  • 1
    Should be noted that cloning a Chars is essentially free, it just duplicates the underlying `slice::Iter` which is two pointers (the current position in the string, and the end pointer). – Masklinn Jul 15 '20 at 08:31
  • That's interesting @Masklinn. I assumed `Clone` would be an expensive operation by default, but it's inexpensive due to it only cloning the following `struct` https://doc.rust-lang.org/src/core/slice/mod.rs.html#3451-3457 with its `ptr` and `end` field? – Jason Jul 15 '20 at 08:42
  • 1
    Yes. `Clone` really means "arbitrary operations", it doesn't necessarily mean expensive e.g. if you `derive(Copy, Clone)` `Clone` is just an alias for `Copy` which is extremely cheap. – Masklinn Jul 15 '20 at 08:44
  • @Masklinn added a note regarding the cheapness, thanks – Kitsu Jul 15 '20 at 08:53
2

There are a few parts you need to understand to understand why you get this error here.

First of all, Rust is all about ownership of values and by default has move semantics instead of copy semantics as you're probably used to from other programming languages.

The Chars struct seems to not implement a common traits?

When a struct implements the Copy trait, then it works using copy semantics. But Chars does not implement the Copy trait, as the error message says. This is of course on purpose and not a flaw or bug in the standard library.

A Chars object is an Iterator over the chars in a string slice. The count method of an Iterator takes self (the value, not a reference &self) as a parameter, which means it takes ownership of the iterator that you call it on. The API documentation also says this:

Consumes the iterator, counting the number of iterations and returning it.

But note that you have a Vec<Chars>, so you have a Vec that owns the Chars objects. You cannot call count() on the Chars object owned by the Vec, because the count() function needs to take over ownership and it can't because the Vec already owns it.

Chars does not implement the Copy trait, but it does implement Clone, so one thing you can do is to explicitly create a copy of the Chars by calling Clone on it:

let longest = char_vec.iter().map(|x| x.clone().count()).max().unwrap_or(0);
                                       ^^^^^^^^ Make an explicit clone of the Chars here

You can then call count() on the clone.

Jesper
  • 202,709
  • 46
  • 318
  • 350
  • Out of interest, wouldn't `into_iter()` also work, except for it consuming `char_vec` by moving it, making it unavailable afterwards? – Jason Jul 15 '20 at 08:24
  • 1
    @Jason Yes, that would also work, but indeed `char_vec` would then be unavailable afterwards. – Jesper Jul 15 '20 at 08:43
  • Thank you for good explanation. I still have trouble with understanding some of the concepts in rust, but I'm getting better. – Djent Jul 16 '20 at 06:08
0

As the other answers explain, the count method consumes the caller object. However, Chars is owned by the parent vector and not a Copy. Another solution is to use iter_mut to iterate the vector so as to let the vector give up the ownership. The side effect is the vector cannot be used afterward.

let mut char_vec = vec!["abc".chars(), "1234567890".chars(), "de".chars()];
    
let longest = char_vec.iter_mut().map(|x| x.count()).max().unwrap_or(0);

Rust Playground

Joe_Jingyu
  • 1,024
  • 6
  • 9