不可变值仍在移动

immutable value is still being moved

我无法编译这个函数:

/// 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()    
}

当前错误是:

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

我认为错误的来源是 chars 是一个迭代器,所以它发生变异,导致无法在闭包中借用,但即使我尝试声明一个局部变量(例如 let count = chars.count()), 我仍然遇到借用错误。

我试过用 & 取消引用它,但也没用。

根据 cc 是 UTF8 还是 ASCII,您可以使用两种略有不同的方法来实现此功能。 UTF8 实现当然适用于这两种情况,因为 UTF8 是 ASCII 的超集。

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

fn maskify_ascii(cc: &str) -> String {
    let mask_idx = cc.len().saturating_sub(4);
    format!("{0:#<1$}{2}", "#", mask_idx, &cc[mask_idx..])
}

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

playground

这里问题的症结在于Char::count()消耗了self。即使您声明了局部变量,在将所有权移至 count 函数后也不能使用 chars

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*
}

您可以通过创建新迭代器并使用它来获取 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");
}

或者你可以用 .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");
}

请注意 str.len() 可以 处理 ascii 而 .chars().count() 可以处理完整的 utf8。

感谢@ibraheem-ahmed,我最终得到了这个解决方案:

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