I created a small crate that validates IBAN's. It is (very originally) called iban_validate. For computing the checksum of an address, I use the following code:
changed_order.chars()
// Convert letters to numbers
.flat_map(|c| c.to_digit(36).unwrap().to_string().chars())
// Convert chars to digits
.map(|c| c.to_digit(10).unwrap())
// Compute modulo
.fold(0, |acc, d| (acc * 10 + d) % 97) as u8
When I run it, I get the following error:
error: borrowed value does not live long enough
--> src/lib.rs:70:23
|
70 | .flat_map(|c| c.to_digit(36).unwrap().to_string().chars())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value only lives until here
| |
| temporary value created here
...
74 | .fold(0, |acc, d| (acc * 10 + d) % 97) as u8
| - temporary value needs to live until here
What am I doing wrong and how can I fix it? Here is a playground where the error occurs: Rust Playground.
to_string() allocates a new string. Rust needs to know when and how to free it.
So it needs to be stored somewhere. Currently you have it as a temporary object in a closure.
You can collect all strings to a vec first, so that they're not "lost".
Perhaps you could append the intermediate digits to a String (without materializing intermediate strings), and then run your fold over that, something like:
let mut s = String::new();
for c in changed_order.chars() {
let _ = write!(&mut s, "{}", c.to_digit(36).unwrap());
}
s.chars()
.map(|c| c.to_digit(10).unwrap())
.fold(0, |acc, d| (acc * 10 + d) % 97) as u8
I like @birkenfeld's simple solution, but I already wrote the following, so I'll show it as another tool for your general toolbox.
Since you know to_digit(36) is always a 1- or 2-digit number, you can just an ArrayVec and have no allocations at all. Something like:
.flat_map(|c| {
let n = c.to_digit(36).unwrap() as u8;
if n >= 10 {
ArrayVec::from([n / 10, n % 10])
} else {
// hopefully this infers the same `ArrayVec<[u8;2]>`,
// but it might need explicit type annotations...
std::iter::once(n).collect()
}
}
In the end, thanks to your help, I could reduce the entire function down to this:
/// This function computes the checksum of an address. The function assumes the string only
/// contains 0-9 and A-Z.
///
/// # Panics
/// If the address contains any characters other than 0-9 or A-Z, this function will panic.
fn compute_checksum(address: &String) -> u8 {
address.chars()
// Move the first four characters to the back
.cycle()
.skip(4)
.take(address.len())
// Calculate the checksum
.fold(0, |acc, c| {
// Convert '0'-'Z' to 0-35
let digit = c.to_digit(36).unwrap();
// If the number consists of two digits, multiply by 100
let multiplier = if digit > 9 { 100 } else { 10 };
// Calculate modulo
(acc * multiplier + digit) % 97
}) as u8
}