Rust Luhn Checksum Project
The Luhn checksum is a small calculation over a card number's digits. It catches most single-digit typing mistakes.
Text becomes digits — or fails as a whole
map turns each character into an Option digit, and collecting into Option<Vec<u32>> fails as a whole on the first non-digit:
let bad: Option<Vec<u32>> = "42x7".chars().map(|c| c.to_digit(10)).collect();
println!("{bad:?}");
let ok: Option<Vec<u32>> = "427".chars().map(|c| c.to_digit(10)).collect();
println!("{ok:?}");Spaces are decoration
The pipeline filters spaces out before anything else happens:
let cleaned: String = "7992 7398".chars().filter(|c| *c != ' ').collect();
println!("{cleaned}");Position from the right decides who doubles
rev() walks the digits right to left. enumerate() numbers them from the right. The digits at odd positions double. Those are the ones that double:
let digits = [1, 2, 3];
for (i, d) in digits.iter().rev().enumerate() {
println!("digit {d} at position {i} from the right");
}Doubling past 9: subtract 9
If doubling gives a two-digit number, subtract 9 — the same as adding its two digits together:
for d in [4, 7] {
let doubled = d * 2;
let fixed = if doubled > 9 { doubled - 9 } else { doubled };
println!("{d} -> {fixed}");
}The number passes when the total ends in 0
Add up all the digits, doubled and plain. One last test decides:
let total = 70;
println!("passes = {}", total % 10 == 0);Example
fn luhn(number: &str) -> bool {
let digits: Option<Vec<u32>> = number
.chars()
.filter(|c| *c != ' ')
.map(|c| c.to_digit(10))
.collect();
let Some(digits) = digits else { return false };
if digits.len() < 2 {
return false;
}
let sum: u32 = digits
.iter()
.rev()
.enumerate()
.map(|(i, &d)| {
if i % 2 == 1 {
let doubled = d * 2;
if doubled > 9 { doubled - 9 } else { doubled }
} else {
d
}
})
.sum();
sum % 10 == 0
}
fn main() {
for number in ["79927398713", "7992 7398 713", "79927398710", "1234", "42x7", "7"] {
println!("{}: {}", number, luhn(number));
}
}Key points
- Fewer than two digits is never a real card number: reject it before summing
- The Luhn checksum checks a card number by adding its digits in a set pattern.
In this example, luhn first turns the text into digits. filter drops the spaces. map turns each character into an Option digit. Collecting into Option<Vec<u32>> can fail. It fails as a whole. It fails if any character is not a digit. That is why 42x7 is rejected. A number with fewer than two digits is rejected next. The sum then walks the digits in reverse. It walks with enumerate. Digits at odd positions are doubled. Those positions count from the right. A doubled value over 9 has 9 subtracted. 79927398713 is the classic test number for this checksum. It passes. The same digits pass, too. They pass when typed with spaces. Change the last digit to 0. Then the total no longer ends in 0. So it fails. That is exactly the typing mistake. The checksum exists to catch it.
Note
This has the same shape as the Mini-Grep project.
It is one small function.
The function can be tested on its own.
Every edge case is decided on purpose.
None is decided by accident.
The cases include empty text.
They include a stray letter.
They include a single digit.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
In the pipeline, what makes the input "42x7" fail?