Rust Challenge: Implementing a Generic Luhn Algorithm Trait

Rust Challenge: Implementing a Generic Luhn Algorithm Trait

Learn how to implement a flexible Luhn algorithm validation trait in Rust. This practical guide covers trait implementation, generic programming, and type conversion in Rust through a real-world example.

ai
ai-team
January 26, 2024
8 min read

Rust Challenge: Implementing the Luhn Trait

In this Rust challenge, we'll implement a flexible trait for the Luhn algorithm. The Luhn algorithm is commonly used for validating various identification numbers, such as credit card numbers, Canadian Social Insurance Numbers, etc.

Challenge Description

The Luhn algorithm can be applied to various data types (strings, integers, etc.). Instead of creating a struct solely for validation purposes, we'll create and implement a custom trait to perform validation directly on different data types.

Prerequisites

You may want to familiarize yourself with the basic Luhn algorithm through these exercises:

exercism download --exercise=luhn --track=rust
exercism download --exercise=luhn-from --track=rust

Solution

Here's an implementation that demonstrates Rust's trait system and generic programming capabilities:

pub trait Luhn {
    fn valid_luhn(&self) -> bool;
}

/// Implement auxiliary functions for Luhn algorithm
fn luhn_digits<I>(digits: I) -> bool
where
    I: IntoIterator<Item = u8>,
{
    let digits: Vec<u8> = digits.into_iter().collect();
    let checksum: u8 = digits
        .iter()
        .rev()
        .enumerate()
        .map(|(i, &digit)| {
            if i % 2 == 1 {
                let doubled = digit * 2;
                if doubled > 9 { doubled - 9 } else { doubled }
            } else {
                digit
            }
        })
        .sum();
    checksum % 10 == 0
}

impl<'a> Luhn for &'a str {
    fn valid_luhn(&self) -> bool {
        let digits: Vec<u8> = self.chars()
            .filter_map(|c| c.to_digit(10).map(|d| d as u8))
            .collect();
        luhn_digits(digits)
    }
}

impl Luhn for u64 {
    fn valid_luhn(&self) -> bool {
        let digits: Vec<u8> = self.to_string()
            .chars()
            .filter_map(|c| c.to_digit(10).map(|d| d as u8))
            .collect();
        luhn_digits(digits)
    }
}

impl Luhn for String {
    fn valid_luhn(&self) -> bool {
        let digits: Vec<u8> = self.chars()
            .filter_map(|c| c.to_digit(10).map(|d| d as u8))
            .collect();
        luhn_digits(digits)
    }
}

impl Luhn for u8 {
    fn valid_luhn(&self) -> bool {
        luhn_digits(vec![*self])
    }
}

impl Luhn for u16 {
    fn valid_luhn(&self) -> bool {
        let digits: Vec<u8> = self.to_string()
            .chars()
            .filter_map(|c| c.to_digit(10).map(|d| d as u8))
            .collect();
        luhn_digits(digits)
    }
}

impl Luhn for u32 {
    fn valid_luhn(&self) -> bool {
        let digits: Vec<u8> = self.to_string()
            .chars()
            .filter_map(|c| c.to_digit(10).map(|d| d as u8))
            .collect();
        luhn_digits(digits)
    }
}

impl Luhn for usize {
    fn valid_luhn(&self) -> bool {
        let digits: Vec<u8> = self.to_string()
            .chars()
            .filter_map(|c| c.to_digit(10).map(|d| d as u8))
            .collect();
        luhn_digits(digits)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_valid_luhn_str() {
        assert!("091".valid_luhn());
        assert!("046 454 286".valid_luhn());
        assert!(!"046 454 287".valid_luhn());
    }

    #[test]
    fn test_valid_luhn_string() {
        assert!(String::from("046 454 286").valid_luhn());
        assert!(!String::from("046 454 287").valid_luhn());
    }

    #[test]
    fn test_valid_luhn_u16() {
        let valid = 64_436u16;
        let invalid = 64_437u16;
        assert!(valid.valid_luhn());
        assert!(!invalid.valid_luhn());
    }

    #[test]
    fn test_valid_luhn_u32() {
        let valid = 46_454_286u32;
        let invalid = 46_454_287u32;
        assert!(valid.valid_luhn());
        assert!(!invalid.valid_luhn());
    }

    #[test]
    fn test_valid_luhn_u64() {
        let valid = 8273_1232_7352_0562u64;
        let invalid = 8273_1232_7352_0569u64;
        assert!(valid.valid_luhn());
        assert!(!invalid.valid_luhn());
    }

    #[test]
    fn test_valid_luhn_u8() {
        assert!(240u8.valid_luhn());
        assert!(!241u8.valid_luhn());
    }

    #[test]
    fn test_valid_luhn_usize() {
        let valid = 8273_1232_7352_0562usize;
        let invalid = 8273_1232_7352_0569usize;
        assert!(valid.valid_luhn());
        assert!(!invalid.valid_luhn());
    }
}