Rust中如何构造无需T实现Hash/Ord的Rc<T>集合实现去重?
解决方案
标准库原生实现方案
你的理解没有问题:Rc指向的堆内存地址只要对应实例没有被销毁就不会发生变化,完全可以基于地址做哈希、判等、排序,不需要依赖T的任何trait实现。
标准库没有直接提供基于地址判重的Rc集合,但你可以通过NewType模式自定义包装类型,手动实现Hash、PartialEq、Eq(需要HashSet的场景),额外实现Ord、PartialOrd(需要BTreeSet的场景)即可,代码如下:
use std::rc::Rc; use std::hash::{Hash, Hasher}; use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; #[derive(Debug, Clone, Eq)] struct RcByAddr<T>(Rc<T>); impl<T> PartialEq for RcByAddr<T> { fn eq(&self, other: &Self) -> bool { // 直接判断两个Rc是否指向同一个堆地址 Rc::ptr_eq(&self.0, &other.0) } } impl<T> Hash for RcByAddr<T> { fn hash<H: Hasher>(&self, state: &mut H) { // 基于堆地址做哈希 Rc::as_ptr(&self.0).hash(state) } } // 如需使用BTreeSet,补充以下两个实现 impl<T> Ord for RcByAddr<T> { fn cmp(&self, other: &Self) -> Ordering { Rc::as_ptr(&self.0).cmp(&Rc::as_ptr(&other.0)) } } impl<T> PartialOrd for RcByAddr<T> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) } }
使用示例:
use std::collections::HashSet; struct Foo { // 任意成员,不需要实现Hash/Ord } fn main() { let a = Rc::new(Foo {}); let b = Rc::clone(&a); let c = Rc::new(Foo {}); let mut set = HashSet::new(); set.insert(RcByAddr(a.clone())); assert!(set.contains(&RcByAddr(b))); // 指向同一个地址,返回true assert!(!set.contains(&RcByAddr(c))); // 指向不同地址,返回false }
注意事项
- 该实现完全安全:
RcByAddr持有Rc<T>的所有权,会保证对应堆内存不会被释放、地址不会被复用,不存在地址重用导致的判重错误。 - 标准库默认的
Rc<T>的Hash、Eq实现是转发给内部T的,所以你之前直接用HashSet<Rc<Foo>>才会要求Foo实现对应trait,自定义包装类型相当于重写了这部分逻辑,完全符合你的需求。 - 不需要额外引入第三方库,上面的自定义实现仅几十行代码,无额外依赖。
内容的提问来源于stack exchange,提问作者aedm
相关产品推荐
相关产品推荐

