Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

immutable value is still being moved

I can't get this function to compile:

/// Return a String with all characters masked as '#' except the last 4.
fn maskify(cc: &str) -> String {
    let chars = cc.to_string().chars();
    chars
        .enumerate()
        .map(|(i, c)| {
            if i > chars.count() - 4 { '#' } else { c }
        })
        .collect()    
}

The current errors are:

error[E0507]: cannot move out of `chars`, a captured variable in an `FnMut` closure
 --> src/lib.rs:7:21
  |
3 |     let chars = cc.to_string().chars();
  |         ----- captured outer variable
...
7 |             if i > &chars.count() - 4 { '#' } else { c }
  |                     ^^^^^ move occurs because `chars` has type `std::str::Chars<'_>`, which does not implement the `Copy` trait

error[E0716]: temporary value dropped while borrowed
 --> src/lib.rs:3:17
  |
3 |     let chars = cc.to_string().chars();
  |                 ^^^^^^^^^^^^^^        - temporary value is freed at the end of this statement
  |                 |
  |                 creates a temporary which is freed while still in use
4 |     chars
  |     ----- borrow later used here
  |
  = note: consider using a `let` binding to create a longer lived value

error[E0382]: use of moved value: `chars`
 --> src/lib.rs:6:14
  |
3 |     let chars = cc.to_string().chars();
  |         ----- move occurs because `chars` has type `std::str::Chars<'_>`, which does not implement the `Copy` trait
4 |     chars
  |     ----- value moved here
5 |         .enumerate()
6 |         .map(|(i, c)| {
  |              ^^^^^^^^ value used here after move
7 |             if i > &chars.count() - 4 { '#' } else { c }
  |                     ----- use occurs due to use in closure

I think the source of the error is that chars is an iterator, so it mutates, making it impossible to borrow in the closure, but even if I try to declare a local variable (such as let count = chars.count()), I still get borrow errors.

I've tried dereferencing it with &, but that didn't work either.

like image 743
mzedeler Avatar asked Mar 02 '23 21:03

mzedeler


1 Answers

The crux of the issue here is that Char::count() consumes self. Even if you declare a local variable, you cannot use chars after you moved ownership to the count function:

fn maskify(cc: &str) {
  let chars = cc.to_string().chars();
   // ^^^^^ move occurs here
  let count = chars.count();
                 // ^^^^^^ `chars` moved because `count` consumes self
  let _ = chars.enumerate();
       // ^^^^^ value used here after move - *this is not allowed*
}

You can fix this issue by creating a new iterator and consuming that to get the count:

fn maskify(cc: &str) -> String {
    let chars = cc.chars();
    let count = cc.chars().count();
             // ^^^ create and consume a new iterator over cc
    chars
        .enumerate()
        .map(|(i, c)| {
            if i < count - 4 { '#' } else { c }
        })
        .collect()    
}

fn main() {
    assert_eq!(maskify("abcd1234"), "####1234");
}

Or you can get the length of the string with .len():

fn maskify(cc: &str) -> String {
    let chars = cc.chars();
    chars
        .enumerate()
        .map(|(i, c)| {
            if i < cc.len() - 4 { '#' } else { c }
        })
        .collect()    
}

fn main() {
    assert_eq!(maskify("abcd1234"), "####1234");
}

Note that str.len() can only handle ascii while .chars().count() can handle full utf8.

like image 105
Ibraheem Ahmed Avatar answered Mar 12 '23 16:03

Ibraheem Ahmed