Rust中为结构体实现两种Eq trait适配BinaryHeap与HashSet
解决Rust结构体同时适配BinaryHeap和HashSet的不同相等/排序需求
Rust不允许同一类型对非泛型trait(比如Eq)实现多套逻辑,所以直接给Obj同时实现两种Eq是不可能的。标准解决方案是用newtype模式,通过包装结构体来分别实现不同的trait逻辑。
具体实现步骤
- 保留原始结构体,不实现冲突的trait:
#[derive(Debug)] struct Obj { id: i32, value: i64, }
- 定义两个包装类型,分别对应两种使用场景:
// 用于BinaryHeap,按value排序 struct ByValue(Obj); // 用于HashSet,按id哈希和判等 struct ById(Obj);
- 为
ByValue实现排序相关的trait(基于value字段):
impl PartialEq for ByValue { fn eq(&self, other: &Self) -> bool { self.0.value == other.0.value } } impl Eq for ByValue {} impl PartialOrd for ByValue { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { // BinaryHeap默认是大顶堆,若要小顶堆可反转顺序(比如other.0.value.partial_cmp(&self.0.value)) self.0.value.partial_cmp(&other.0.value) } } impl Ord for ByValue { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other).unwrap() } }
- 为
ById实现哈希和判等相关的trait(基于id字段):
use std::hash::{Hash, Hasher}; impl PartialEq for ById { fn eq(&self, other: &Self) -> bool { self.0.id == other.0.id } } impl Eq for ById {} impl Hash for ById { fn hash<H: Hasher>(&self, state: &mut H) { self.0.id.hash(state); } }
使用示例
use std::collections::{BinaryHeap, HashSet}; fn main() { let obj1 = Obj { id: 1, value: 100 }; let obj2 = Obj { id: 2, value: 200 }; let obj3 = Obj { id: 1, value: 300 }; // 和obj1同id不同value // 放入BinaryHeap(按value排序) let mut heap = BinaryHeap::new(); heap.push(ByValue(obj1.clone())); heap.push(ByValue(obj2.clone())); heap.push(ByValue(obj3.clone())); while let Some(val) = heap.pop() { println!("Heap弹出: {:?}", val.0); // 输出顺序应为obj3(value=300)、obj2(200)、obj1(100) } // 放入HashSet(按id去重) let mut set = HashSet::new(); set.insert(ById(obj1)); set.insert(ById(obj2)); let insert_result = set.insert(ById(obj3)); // 因为id=1已存在,插入失败 println!("HashSet大小: {}", set.len()); // 输出2 println!("obj3插入结果: {}", insert_result); // 输出false }
补充说明
- newtype是零开销的,Rust编译器会把它优化成原始结构体,不会有性能损失。
- 如果需要频繁访问内部
Obj的字段,可以为包装类型实现Dereftrait,直接通过*访问:
use std::ops::Deref; impl Deref for ByValue { type Target = Obj; fn deref(&self) -> &Self::Target { &self.0 } } // 使用时可以直接写 `val.id` 代替 `val.0.id`
内容的提问来源于stack exchange,提问作者Rufus
相关产品推荐
相关产品推荐

