Rust 练习册 :Luhn From与From trait
在Rust中,类型转换是一个重要概念,它允许我们在不同类型之间进行安全的转换。From trait是Rust标准库中用于类型转换的核心trait之一,它定义了如何从一种类型创建另一种类型。在 Exercism 的 “luhn-from” 练习中,我们需要实现From trait来为Luhn结构体创建多种类型的转换。这不仅能帮助我们掌握Rust的类型系统和trait实现,还能深入学习如何设计灵活且可扩展的API。
什么是From trait?
From trait是Rust标准库中的一个核心trait,用于定义从一种类型到另一种类型的转换。它的定义如下:
trait From<T> {fn from(input: T) -> Self;
}
From trait有几个重要特点:
- 安全转换:From trait保证转换不会失败
- 自动实现Into:实现From trait会自动获得对应的Into trait
- 语义明确:表示从某种类型创建目标类型的"自然"转换
在我们的练习中,需要为Luhn结构体实现From trait,使其能够从多种类型(如&str、String、各种整数类型)创建Luhn实例。
让我们先看看练习提供的结构和函数签名:
pub struct Luhn;impl Luhn {pub fn is_valid(&self) -> bool {unimplemented!("Determine if the current Luhn struct contains a valid credit card number.");}
}/// Here is the example of how the From trait could be implemented
/// for the &str type. Naturally, you can implement this trait
/// by hand for the every other type presented in the test suite,
/// but your solution will fail if a new type is presented.
/// Perhaps there exists a better solution for this problem?
impl<'a> From<&'a str> for Luhn {fn from(input: &'a str) -> Self {unimplemented!("From the given input '{}' create a new Luhn struct.", input);}
}
我们需要实现Luhn结构体及其From trait,使其能够从多种类型创建实例并验证Luhn校验和。
设计分析
1. 核心要求
- 类型转换:实现From trait以支持多种类型到Luhn的转换
- 数据存储:在Luhn结构体中存储原始数据
- 校验算法:实现Luhn校验和验证算法
- API设计:设计简洁且易于使用的API
2. 技术要点
- 泛型实现:使用泛型和trait约束减少重复代码
- 字符串处理:处理各种类型的输入数据
- 生命周期管理:正确处理引用的生命周期
- 代码复用:避免为每种类型重复实现相同逻辑
完整实现
1. 基础实现
pub struct Luhn {code: String,
}impl Luhn {pub fn is_valid(&self) -> bool {// 移除空格并验证字符let cleaned: String = self.code.chars().filter(|c| !c.is_whitespace()).collect();// 检查长度和字符有效性if cleaned.len() <= 1 || !cleaned.chars().all(|c| c.is_ascii_digit()) {return false;}let digits: Vec<u32> = cleaned.chars().map(|c| c.to_digit(10).unwrap()).collect();let mut sum = 0;let len = digits.len();for (i, &digit) in digits.iter().enumerate() {// 从右到左,偶数位置的数字需要乘以2if (len - i) % 2 == 0 {let doubled = digit * 2;sum += if doubled > 9 { doubled - 9 } else { doubled };} else {sum += digit;}}sum % 10 == 0}
}impl<'a> From<&'a str> for Luhn {fn from(input: &'a str) -> Self {Luhn {code: input.to_string(),}}
}impl From<String> for Luhn {fn from(input: String) -> Self {Luhn { code: input }}
}impl From<u8> for Luhn {fn from(input: u8) -> Self {Luhn {code: input.to_string(),}}
}impl From<u16> for Luhn {fn from(input: u16) -> Self {Luhn {code: input.to_string(),}}
}impl From<u32> for Luhn {fn from(input: u32) -> Self {Luhn {code: input.to_string(),}}
}impl From<u64> for Luhn {fn from(input: u64) -> Self {Luhn {code: input.to_string(),}}
}impl From<usize> for Luhn {fn from(input: usize) -> Self {Luhn {code: input.to_string(),}}
}
2. 使用泛型优化的实现
pub struct Luhn {code: String,
}impl Luhn {pub fn is_valid(&self) -> bool {let digits: Vec<u32> = self.code.chars().filter(|c| !c.is_whitespace()).map(|c| c.to_digit(10)).collect::<Option<Vec<u32>>>().unwrap_or_default();// 必须至少有2位数字if digits.len() <= 1 {return false;}let sum: u32 = digits.iter().rev() // 从右到左处理.enumerate().map(|(i, &digit)| {if i % 2 == 1 { // 偶数位置(从右数第2、4、6...位)let doubled = digit * 2;if doubled > 9 { doubled - 9 } else { doubled }} else {digit}}).sum();sum % 10 == 0}
}// 为字符串类型实现From trait
impl<'a> From<&'a str> for Luhn {fn from(input: &'a str) -> Self {Luhn {code: input.to_string(),}}
}impl From<String> for Luhn {fn from(input: String) -> Self {Luhn { code: input }}
}// 为数字类型实现From trait
impl From<u8> for Luhn {fn from(input: u8) -> Self {Luhn {code: input.to_string(),}}
}impl From<u16> for Luhn {fn from(input: u16) -> Self {Luhn {code: input.to_string(),}}
}impl From<u32> for Luhn {fn from(input: u32) -> Self {Luhn {code: input.to_string(),}}
}impl From<u64> for Luhn {fn from(input: u64) -> Self {Luhn {code: input.to_string(),}}
}impl From<usize> for Luhn {fn from(input: usize) -> Self {Luhn {code: input.to_string(),}}
}
3. 使用宏减少重复代码的实现
pub struct Luhn {code: String,
}impl Luhn {pub fn is_valid(&self) -> bool {let digits: Vec<u32> = self.code.chars().filter(|c| !c.is_whitespace()).map(|c| c.to_digit(10)).collect::<Option<Vec<u32>>>().unwrap_or_default();if digits.len() <= 1 {return false;}let sum: u32 = 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();sum % 10 == 0}
}// 为字符串类型实现From trait
impl<'a> From<&'a str> for Luhn {fn from(input: &'a str) -> Self {Luhn {code: input.to_string(),}}
}impl From<String> for Luhn {fn from(input: String) -> Self {Luhn { code: input }}
}// 使用宏为数字类型实现From trait
macro_rules! impl_from_number {($($t:ty),*) => {$(impl From<$t> for Luhn {fn from(input: $t) -> Self {Luhn {code: input.to_string(),}}})*};
}impl_from_number!(u8, u16, u32, u64, usize);
测试用例分析
通过查看测试用例,我们可以更好地理解需求:
#[test]
fn you_can_validate_from_a_str() {let valid = Luhn::from("046 454 286");let invalid = Luhn::from("046 454 287");assert!(valid.is_valid());assert!(!invalid.is_valid());
}
可以从&str创建Luhn实例并验证。
#[test]
fn you_can_validate_from_a_string() {let valid = Luhn::from(String::from("046 454 286"));let invalid = Luhn::from(String::from("046 454 287"));assert!(valid.is_valid());assert!(!invalid.is_valid());
}
可以从String创建Luhn实例并验证。
#[test]
fn you_can_validate_from_a_u8() {let valid = Luhn::from(240u8);let invalid = Luhn::from(241u8);assert!(valid.is_valid());assert!(!invalid.is_valid());
}
可以从u8创建Luhn实例并验证。
#[test]
fn you_can_validate_from_a_u16() {let valid = Luhn::from(64_436u16);let invalid = Luhn::from(64_437u16);assert!(valid.is_valid());assert!(!invalid.is_valid());
}
可以从u16创建Luhn实例并验证。
#[test]
fn single_digit_string_is_invalid() {assert!(!Luhn::from("1").is_valid());
}
单个数字的字符串是无效的。
#[test]
fn strings_that_contain_non_digits_are_invalid() {assert!(!Luhn::from("046a 454 286").is_valid());
}
包含非数字字符的字符串是无效的。
性能优化版本
考虑性能的优化实现:
pub struct Luhn {code: String,
}impl Luhn {pub fn is_valid(&self) -> bool {let mut digits = Vec::with_capacity(self.code.len());// 预分配并验证字符for c in self.code.chars() {match c {' ' => continue, // 跳过空格'0'..='9' => digits.push(c as u32 - '0' as u32),_ => return false, // 非数字字符}}// 至少需要2位数字if digits.len() <= 1 {return false;}let mut sum = 0u32;// 从右到左处理,使用索引计算位置for (i, &digit) in digits.iter().rev().enumerate() {if i % 2 == 1 { // 偶数位置(从右数第2、4、6...位)let doubled = digit * 2;sum += if doubled > 9 { doubled - 9 } else { doubled };} else {sum += digit;}}sum % 10 == 0}
}// 为字符串类型实现From trait
impl<'a> From<&'a str> for Luhn {fn from(input: &'a str) -> Self {Luhn {code: input.to_string(),}}
}impl From<String> for Luhn {fn from(input: String) -> Self {Luhn { code: input }}
}// 使用宏为数字类型实现From trait
macro_rules! impl_from_number {($($t:ty),*) => {$(impl From<$t> for Luhn {fn from(input: $t) -> Self {Luhn {code: input.to_string(),}}})*};
}impl_from_number!(u8, u16, u32, u64, usize);
错误处理和边界情况
考虑更多边界情况的实现:
#[derive(Debug)]
pub struct Luhn {code: String,
}impl Luhn {pub fn is_valid(&self) -> bool {self.validate().unwrap_or(false)}pub fn validate(&self) -> Result<bool, LuhnError> {if self.code.is_empty() {return Err(LuhnError::EmptyInput);}let mut digits = Vec::new();for c in self.code.chars() {if c.is_whitespace() {continue;}match c.to_digit(10) {Some(digit) => digits.push(digit),None => return Err(LuhnError::InvalidCharacter(c)),}}if digits.len() <= 1 {return Err(LuhnError::TooShort);}let sum: u32 = 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();Ok(sum % 10 == 0)}// 获取原始代码pub fn code(&self) -> &str {&self.code}
}#[derive(Debug, PartialEq)]
pub enum LuhnError {TooShort,InvalidCharacter(char),EmptyInput,
}// 为字符串类型实现From trait
impl<'a> From<&'a str> for Luhn {fn from(input: &'a str) -> Self {Luhn {code: input.to_string(),}}
}impl From<String> for Luhn {fn from(input: String) -> Self {Luhn { code: input }}
}// 使用宏为数字类型实现From trait
macro_rules! impl_from_number {($($t:ty),*) => {$(impl From<$t> for Luhn {fn from(input: $t) -> Self {Luhn {code: input.to_string(),}}})*};
}impl_from_number!(u8, u16, u32, u64, usize);
扩展功能
基于基础实现,我们可以添加更多功能:
pub struct Luhn {code: String,
}impl Luhn {pub fn is_valid(&self) -> bool {self.validate().unwrap_or(false)}pub fn validate(&self) -> Result<bool, LuhnError> {if self.code.is_empty() {return Err(LuhnError::EmptyInput);}let mut digits = Vec::new();for c in self.code.chars() {if c.is_whitespace() {continue;}match c.to_digit(10) {Some(digit) => digits.push(digit),None => return Err(LuhnError::InvalidCharacter(c)),}}if digits.len() <= 1 {return Err(LuhnError::TooShort);}let sum: u32 = 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();Ok(sum % 10 == 0)}// 获取原始代码pub fn code(&self) -> &str {&self.code}// 获取清理后的数字pub fn clean_digits(&self) -> Result<Vec<u32>, LuhnError> {let mut digits = Vec::new();for c in self.code.chars() {if c.is_whitespace() {continue;}match c.to_digit(10) {Some(digit) => digits.push(digit),None => return Err(LuhnError::InvalidCharacter(c)),}}Ok(digits)}// 生成校验位pub fn generate_check_digit(&self) -> Option<u32> {let digits = self.clean_digits().ok()?;// 添加一个占位符作为校验位let mut extended_digits = digits.clone();extended_digits.push(0);let sum: u32 = extended_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();let check_digit = (10 - (sum % 10)) % 10;Some(check_digit)}
}#[derive(Debug, PartialEq)]
pub enum LuhnError {TooShort,InvalidCharacter(char),EmptyInput,
}// 通用的From实现
pub trait LuhnInput {fn to_luhn_string(self) -> String;
}impl LuhnInput for &str {fn to_luhn_string(self) -> String {self.to_string()}
}impl LuhnInput for String {fn to_luhn_string(self) -> String {self}
}impl<T> LuhnInput for T
whereT: ToString,
{fn to_luhn_string(self) -> String {self.to_string()}
}impl<T: LuhnInput> From<T> for Luhn {fn from(input: T) -> Self {Luhn {code: input.to_luhn_string(),}}
}
实际应用场景
From trait和Luhn算法在实际开发中有以下应用:
- 支付系统:验证信用卡号和借记卡号
- API设计:创建灵活且易于使用的API
- 数据验证:验证各种识别号码的准确性
- 表单处理:处理用户输入的不同数据类型
- 库开发:设计可扩展的库接口
- 类型安全:确保类型转换的安全性
- 移动设备:验证IMEI号码
- 身份验证:验证各种身份证件号码
算法复杂度分析
-
时间复杂度:O(n)
- 需要遍历字符串中的每个字符,其中n是字符串长度
-
空间复杂度:O(n)
- 需要存储过滤后的数字数组
与其他实现方式的比较
// 使用Deref trait的实现
use std::ops::Deref;pub struct Luhn {code: String,
}impl Deref for Luhn {type Target = str;fn deref(&self) -> &Self::Target {&self.code}
}impl Luhn {pub fn is_valid(&self) -> bool {// Luhn算法实现let digits: Vec<u32> = self.chars().filter(|c| !c.is_whitespace()).map(|c| c.to_digit(10)).collect::<Option<Vec<u32>>>().unwrap_or_default();if digits.len() <= 1 {return false;}let sum: u32 = 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();sum % 10 == 0}
}// 使用Builder模式的实现
pub struct LuhnBuilder {code: String,
}impl LuhnBuilder {pub fn new() -> Self {LuhnBuilder {code: String::new(),}}pub fn code(mut self, code: &str) -> Self {self.code = code.to_string();self}pub fn build(self) -> Luhn {Luhn { code: self.code }}
}pub struct Luhn {code: String,
}impl Luhn {pub fn builder() -> LuhnBuilder {LuhnBuilder::new()}pub fn is_valid(&self) -> bool {// Luhn算法实现let digits: Vec<u32> = self.code.chars().filter(|c| !c.is_whitespace()).map(|c| c.to_digit(10)).collect::<Option<Vec<u32>>>().unwrap_or_default();if digits.len() <= 1 {return false;}let sum: u32 = 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();sum % 10 == 0}
}// 使用泛型和trait约束的实现
pub struct Luhn<T: ToString> {data: T,
}impl<T: ToString> Luhn<T> {pub fn new(data: T) -> Self {Luhn { data }}pub fn is_valid(&self) -> bool {let code = self.data.to_string();// Luhn算法实现...true // 简化实现}
}
总结
通过 luhn-from 练习,我们学到了:
- From trait:掌握了Rust中类型转换的核心概念
- 泛型编程:学会了使用泛型减少重复代码
- 宏系统:了解了如何使用宏简化重复实现
- API设计:学会了设计灵活且易于使用的API
- 类型系统:深入理解了Rust的类型系统和trait系统
- 代码复用:学会了如何避免重复代码并提高可维护性
这些技能在实际开发中非常有用,特别是在设计库API、处理类型转换、构建可扩展系统等场景中。From trait虽然是Rust标准库中的一个基础trait,但它体现了Rust类型系统的设计哲学,是学习Rust高级特性的良好起点。
通过这个练习,我们也看到了Rust在类型转换和API设计方面的强大能力,以及如何用安全且灵活的方式实现类型转换系统。这种结合了安全性和灵活性的语言特性正是Rust的魅力所在。
