不可变值仍在被移动。

4
我无法编译此函数:
/// 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()),我仍然会遇到借用错误。

我已经尝试使用 & 进行解引用,但也没有起作用。

3个回答

4
问题的关键在于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。

2
我会使用 cc.chars().count() 而不是 cc.len(),以更好地支持 Unicode。(虽然这仍然无法处理所有情况,但更接近了。) - Lambda Fairy
很好的解释 - 是的 - 我也注意到这里的 len 不正确,但是添加另一个 count() 就可以解决这个问题。 - mzedeler
尽管请注意,即使使用代码点版本也会因为多代码点字符而破坏,例如上面的“ देवनागरी”有8个代码点但是5个“字符”(形状集群),所以即使使用代码点计数进行掩蔽,结果仍然是错误的:“####ागरी”只有原始字符的2.5个(最后两个“字符”和一个附加在上的组合代码点)。 - Masklinn
1
因此,你最终的结论应该是 str.len() 只处理 ASCII 字符,否则它完全失效(并且可以说在尝试操作字符时它根本就是有问题的)。如果使用 str::len,该函数可能会在获取非 ASCII 码点时失败,无论是完全崩溃还是只导致一个 Err - Masklinn
另一个次要问题:如果迭代器向后使用,我想我们可以节省一次扫描长度,因为我们只需输出前四个字符并输出“#”以表示剩余内容。 - mzedeler
显示剩余4条评论

3

针对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");
}

编程游乐场


0
感谢 @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()    
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接