Rust中避免定义近乎相同枚举时的代码重复方案
Rust中实现仅排序逻辑不同的相似枚举的最优方案
问题背景
需要定义两个近乎完全相同的枚举,仅partial_cmp方法(排序逻辑)存在差异。面向对象语言中通常用子类继承修改方法,但Rust不支持继承,此前尝试的trait方案存在两个痛点:
- 重复定义枚举值,枚举值数量多时维护成本高
- 依赖枚举值的公共方法(如
From<char>)需要重复编写
可行解决方案
方案1:组合式设计——内部共享枚举+排序策略标记
核心思路是把枚举的公共部分提取为一个内部枚举,再用带排序策略标记的外层结构体包裹,复用内部枚举的所有公共逻辑,仅针对不同策略实现排序。
代码实现
// 共享的内部枚举,包含所有枚举值和公共逻辑 #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum SharedEnum { A, B, C, } // 公共的From<char>实现,只写一次 impl From<char> for SharedEnum { fn from(value: char) -> Self { match value { 'A' => Self::A, 'B' => Self::B, 'C' => Self::C, _ => panic!("unexpected char: {value}"), } } } // 公共方法的trait及实现 trait MyTrait { fn some_common_method(&self) -> String; } impl MyTrait for SharedEnum { fn some_common_method(&self) -> String { format!("Handling {:?}", self) } } // 排序策略标记:零大小类型,仅用于区分排序逻辑,不占用内存 #[derive(Debug, Clone, Copy)] struct DefaultOrder; #[derive(Debug, Clone, Copy)] struct ReverseOrder; // 带排序策略的外层包装结构体 #[derive(Debug, Clone, Copy, PartialEq, Eq)] struct EnumWithOrder<Order>(SharedEnum, std::marker::PhantomData<Order>); // 转发From<char>到内部枚举 impl<Order> From<char> for EnumWithOrder<Order> { fn from(value: char) -> Self { Self(SharedEnum::from(value), std::marker::PhantomData) } } // 转发MyTrait方法到内部枚举 impl<Order> MyTrait for EnumWithOrder<Order> { fn some_common_method(&self) -> String { self.0.some_common_method() } } // 为默认排序策略实现PartialOrd和Ord impl PartialOrd for EnumWithOrder<DefaultOrder> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { self.0.partial_cmp(&other.0) // 使用内部枚举的自然顺序 } } impl Ord for EnumWithOrder<DefaultOrder> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other).unwrap() } } // 为自定义排序策略实现PartialOrd和Ord(示例:反转顺序) impl PartialOrd for EnumWithOrder<ReverseOrder> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { other.0.partial_cmp(&self.0) // 反转自然顺序 } } impl Ord for EnumWithOrder<ReverseOrder> { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other).unwrap() } }
使用示例
fn main() { let a_default: EnumWithOrder<DefaultOrder> = 'A'.into(); let b_default: EnumWithOrder<DefaultOrder> = 'B'.into(); println!("a_default < b_default: {}", a_default < b_default); // 输出true let a_reverse: EnumWithOrder<ReverseOrder> = 'A'.into(); let b_reverse: EnumWithOrder<ReverseOrder> = 'B'.into(); println!("a_reverse < b_reverse: {}", a_reverse < b_reverse); // 输出false }
方案2:使用宏自动生成重复代码
如果希望直接得到两个独立的枚举,可通过宏自动生成枚举定义、公共方法实现,仅手动传入不同的partial_cmp逻辑。
代码实现
// 定义生成枚举的宏,将公共逻辑封装在宏内 macro_rules! define_ordered_enum { ($enum_name:ident, $cmp_logic:block) => { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum $enum_name { A, B, C, } impl From<char> for $enum_name { fn from(value: char) -> Self { match value { 'A' => Self::A, 'B' => Self::B, 'C' => Self::C, _ => panic!("unexpected char: {value}"), } } } trait MyTrait { fn some_common_method(&self) -> String; } impl MyTrait for $enum_name { fn some_common_method(&self) -> String { format!("Handling {:?}", self) } } impl PartialOrd for $enum_name { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { $cmp_logic } } impl Ord for $enum_name { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other).unwrap() } } }; } // 生成第一个枚举:使用默认排序逻辑 define_ordered_enum!(MyEnum1, { match (self, other) { (Self::A, Self::A) => Some(std::cmp::Ordering::Equal), (Self::A, _) => Some(std::cmp::Ordering::Less), (_, Self::A) => Some(std::cmp::Ordering::Greater), (Self::B, Self::B) => Some(std::cmp::Ordering::Equal), (Self::B, Self::C) => Some(std::cmp::Ordering::Less), (Self::C, Self::B) => Some(std::cmp::Ordering::Greater), (Self::C, Self::C) => Some(std::cmp::Ordering::Equal), } }); // 生成第二个枚举:使用自定义反转排序逻辑 define_ordered_enum!(MyEnum2, { match (self, other) { (Self::A, Self::A) => Some(std::cmp::Ordering::Equal), (Self::A, _) => Some(std::cmp::Ordering::Greater), (_, Self::A) => Some(std::cmp::Ordering::Less), (Self::B, Self::B) => Some(std::cmp::Ordering::Equal), (Self::B, Self::C) => Some(std::cmp::Ordering::Greater), (Self::C, Self::B) => Some(std::cmp::Ordering::Less), (Self::C, Self::C) => Some(std::cmp::Ordering::Equal), } });
方案对比
- 组合式方案:更符合Rust的组合优先设计思想,无重复代码,类型安全,枚举值数量多时优势明显;缺点是需要额外的包装类型,使用时需指定排序标记。
- 宏方案:生成独立枚举,使用方式和普通枚举一致,无需额外包装;缺点是宏的可读性稍弱,修改枚举值时需同步修改宏内部定义。
内容的提问来源于stack exchange,提问作者SPH
相关产品推荐
相关产品推荐

