You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

问题分析

  1. 对象安全限制:OneKeyTrait继承了Sized约束,但动态 trait 对象(dyn Trait)本身不满足Sized,无法作为HashMap的键值类型。
  2. 静态可变变量风险:static mut线程不安全,直接操作会触发未定义行为,必须配合同步原语使用。
  3. 所有权无法共享:原代码中One<T>直接持有T的所有权,缓存实例无法被多个One共享,需要引用计数类型实现共享所有权。
  4. 类型擦除的局限性:用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的检查要求。
  • 灵活的类型擦除:ErasedEqHash trait 让缓存可以存储不同类型的对象,同时保留等价性和哈希能力。

内容的提问来源于stack exchange,提问作者user3310334

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 06:20:33