Reputation: 4371
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.
Upvotes: 4
Views: 150
Reputation: 13618
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.
Upvotes: 4
Reputation: 15165
Two slightly different approaches you can use to implement this function depending on whether or not cc
is UTF8 or ASCII. The UTF8 implementation of course works for both cases as UTF8 is a superset of 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");
}
Upvotes: 3
Reputation: 4371
Thanks to @ibraheem-ahmed, I wound up with this solution:
/// 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()
}
Upvotes: 0