如何在Rust中实现缓存智能指针?
在Rust中实现缓存智能指针
我想实现一个缓存智能指针,让任何等价的对象都能作为同一实例的引用处理,最终能用std::ptr::eq做等价检查,而不是一直用PartialEq。
期望实现的功能如下:
let object = String::from("hello"); let first = One::new(object); let second = One::new(object); assert!(std::ptr::eq(first, second));
以下是我尝试实现的代码:
use std::collections::HashMap; use std::hash::Hash; trait OneKeyTrait: Hash + Sized {} static mut cache: HashMap<dyn OneKeyTrait, One<dyn OneKeyTrait>> = HashMap::new(); pub struct One<T: OneKeyTrait> { count: usize, value: T, } impl<T: OneKeyTrait> One<T> { fn new(x: T) -> One<T> { if cache.contains_key(&x) { cache.get_mut(&x).count += 1; cache.get(&x) } else { let one_x = One { count: 1, value: x, }; cache.insert(x, one_x); one_x } } } impl<T: OneKeyTrait> std::ops::Deref for One<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.value } } impl<T: OneKeyTrait> Drop for One<T> { fn drop(&mut self) { let mut one_x = cache.get_mut(&self.value); one_x.count -= 1; if one_x.count == 0 { cache.remove(&self.value); } } } fn main() { let object = String::from("hello"); let first = One::new(object); let second = One::new(object); assert!(std::ptr::eq(first, second)); }
但代码无法编译,报错信息如下:
error[E0277]: the size for values of type `(dyn OneKeyTrait + 'static)` cannot be known at compilation time --> one.rs:6:19 | 6 | static mut cache: HashMap<dyn OneKeyTrait, One<dyn OneKeyTrait>> = HashMap::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | = help: the trait `Sized` is not implemented for `(dyn OneKeyTrait + 'static)` note: required by a bound in `HashMap` error[E0038]: the trait `OneKeyTrait` cannot be made into an object --> x.rs:6:48 | 6 | static mut cache: HashMap<dyn OneKeyTrait, One<dyn OneKeyTrait>> = HashMap::new(); | ^^^^^^^^^^^^^^^ `OneKeyTrait` cannot be made into an object | note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety> --> one.rs:4:27 | 4 | trait OneKeyTrait: Hash + Sized {} | ----------- ^^^^^ ...because it requires `Self: Sized` | | | this trait cannot be made into an object... = help: consider moving `hash` to another trait
问题分析
- 对象安全限制:
OneKeyTrait继承了Sized约束,但动态 trait 对象(dyn Trait)本身不满足Sized,无法作为HashMap的键值类型。 - 静态可变变量风险:
static mut线程不安全,直接操作会触发未定义行为,必须配合同步原语使用。 - 所有权无法共享:原代码中
One<T>直接持有T的所有权,缓存实例无法被多个One共享,需要引用计数类型实现共享所有权。 - 类型擦除的局限性:用
dyn Trait作为键无法区分不同类型的等价对象,缓存需要按类型隔离存储。
修正后的实现
我们可以借助Arc实现共享所有权,结合OnceLock和Mutex实现线程安全的全局缓存,同时为每个类型单独维护缓存实例:
use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::sync::{Arc, Mutex, OnceLock}; use std::ptr; // 全局缓存:每个类型对应一个HashMap,存储已缓存的Arc<T> static CACHE: OnceLock<Mutex<HashMap<u64, Arc<dyn ErasedEqHash>>>> = OnceLock::new(); // 用于类型擦除的trait,保留等价性和哈希能力 trait ErasedEqHash: Send + Sync { fn eq(&self, other: &dyn ErasedEqHash) -> bool; fn hash(&self, hasher: &mut dyn Hasher); } impl<T: Eq + Hash + Send + Sync + 'static> ErasedEqHash for T { fn eq(&self, other: &dyn ErasedEqHash) -> bool { other.downcast_ref::<T>().map_or(false, |o| self == o) } fn hash(&self, hasher: &mut dyn Hasher) { // 加入类型ID,确保不同类型即使哈希值相同也不会混淆 std::any::TypeId::of::<T>().hash(hasher); self.hash(hasher); } } // 计算包含类型信息的全局哈希值 fn compute_global_hash<T: Eq + Hash + 'static>(value: &T) -> u64 { let mut hasher = std::collections::hash_map::DefaultHasher::new(); std::any::TypeId::of::<T>().hash(&mut hasher); value.hash(&mut hasher); hasher.finish() } #[derive(Clone)] pub struct One<T: Eq + Hash + Send + Sync + 'static>(Arc<T>); impl<T: Eq + Hash + Send + Sync + 'static> One<T> { pub fn new(value: T) -> Self { let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new())); let mut guard = cache.lock().unwrap(); let hash = compute_global_hash(&value); // 检查缓存中是否存在对应实例 if let Some(erased) = guard.get(&hash) { if let Some(arc) = erased.downcast_ref::<T>() { return One(arc.clone()); } } // 缓存中无对应实例,创建新的Arc并存入缓存 let arc = Arc::new(value); guard.insert(hash, arc.clone() as Arc<dyn ErasedEqHash>); One(arc) } } impl<T: Eq + Hash + Send + Sync + 'static> std::ops::Deref for One<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } fn main() { let object1 = String::from("hello"); let first = One::new(object1); let object2 = String::from("hello"); let second = One::new(object2); // 验证指针指向同一实例 assert!(ptr::eq(&*first, &*second)); assert!(ptr::eq(first.0.as_ref(), second.0.as_ref())); }
关键说明
- 类型安全隔离:通过在哈希值中加入类型ID,避免不同类型的等价对象被错误匹配。
- 线程安全保障:用
OnceLock初始化全局缓存,Mutex保证多线程环境下的安全访问。 - 共享所有权实现:
Arc提供引用计数功能,多个One实例共享同一底层对象,满足std::ptr::eq的检查要求。 - 灵活的类型擦除:
ErasedEqHashtrait 让缓存可以存储不同类型的对象,同时保留等价性和哈希能力。
内容的提问来源于stack exchange,提问作者user3310334
相关产品推荐
相关产品推荐

