Rust中使用dyn trait作为HashMap key触发E0038错误如何解决?
问题原因
你遇到的E0038错误是因为Hash trait不符合trait对象安全要求:Hash的核心方法fn hash<H: Hasher>(&self, state: &mut H)带有泛型参数H,泛型方法需要在编译期单态化,无法通过trait对象做动态分发,因此不能作为trait的超trait来构造trait对象。
解决方案
根据你的业务需求,可以选择以下两种常用方案:
方案1:按实例身份索引(性能最优,实现简单)
如果你的聚合需求是按结构体实例的唯一身份统计,也就是同一个Rc指向的实例算同一个key,不同实例就算内容相同也算不同key,那直接用Rc的裸指针地址作为哈希和比较的依据即可,不需要修改MyTrait的定义:
use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::rc::Rc; use std::cmp; trait MyTrait { // 保留原有trait方法,不需要继承Hash } struct MyTraitKey { val: Rc<dyn MyTrait> } impl cmp::PartialEq for MyTraitKey { fn eq(&self, other: &Self) -> bool { // 比较指针地址判断是否是同一个实例 std::ptr::eq(self.val.as_ptr(), other.val.as_ptr()) } } impl cmp::Eq for MyTraitKey {} impl Hash for MyTraitKey { fn hash<H: Hasher>(&self, state: &mut H) { // 哈希指针地址 std::ptr::hash(self.val.as_ptr(), state); } } #[derive(Debug)] struct Type1; impl MyTrait for Type1 {} #[derive(Debug)] struct Type2(u8); impl MyTrait for Type2 {} fn main() { let mut container: HashMap<MyTraitKey,i32> = HashMap::new(); container.insert(MyTraitKey {val: Rc::new(Type1 {})}, 3); }
该方案不需要修改原有trait定义,也不需要给实现MyTrait的结构体加额外约束,性能最高。
方案2:按实例内容索引
如果你需要按实例的内容统计,也就是不同实例只要内容相同就算同一个key,那可以在MyTrait中定义dyn安全的哈希、相等比较方法,绕开泛型约束:
use std::any::Any; use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::rc::Rc; use std::cmp; trait MyTrait: Any { // dyn安全的哈希方法,参数用动态分发的Hasher fn dyn_hash(&self, state: &mut dyn Hasher); // dyn安全的相等比较方法 fn dyn_eq(&self, other: &dyn MyTrait) -> bool; // 支持向下转型 fn as_any(&self) -> &dyn Any { self } } // 为所有实现了Hash+Eq的类型自动实现MyTrait的默认方法 impl<T: Hash + Eq + 'static> MyTrait for T { fn dyn_hash(&self, mut state: &mut dyn Hasher) { self.hash(&mut state); } fn dyn_eq(&self, other: &dyn MyTrait) -> bool { // 先判断类型是否一致,再转型比较内容 if let Some(other) = other.as_any().downcast_ref::<T>() { self == other } else { false } } } struct MyTraitKey { val: Rc<dyn MyTrait> } impl cmp::PartialEq for MyTraitKey { fn eq(&self, other: &Self) -> bool { self.val.dyn_eq(&*other.val) } } impl cmp::Eq for MyTraitKey {} impl Hash for MyTraitKey { fn hash<H: Hasher>(&self, state: &mut H) { self.val.dyn_hash(state); } } #[derive(PartialEq, Eq, Hash, Debug)] struct Type1; impl MyTrait for Type1 {} #[derive(PartialEq, Eq, Hash, Debug)] struct Type2(u8); impl MyTrait for Type2 {} fn main() { let mut container: HashMap<MyTraitKey,i32> = HashMap::new(); // 两个内容相同的Type2会被识别为同一个key container.insert(MyTraitKey {val: Rc::new(Type2(1))}, 3); container.insert(MyTraitKey {val: Rc::new(Type2(1))}, 5); // 输出为Some(5),符合内容相同覆盖的预期 println!("{:?}", container.get(&MyTraitKey {val: Rc::new(Type2(1))})); }
该方案可以实现按内容聚合,需要原有实现MyTrait的结构体都实现Hash和Eq。
内容的提问来源于stack exchange,提问作者devil0150
相关产品推荐
相关产品推荐

