我对寻找或实现Rust数据结构感兴趣,该结构提供了一种零成本的方式来记忆具有任意输出类型T
的单个计算。具体来说,我希望使用以下基本API,以其内部数据不占用Cache<T>
的通用类型Option<T>
:
impl<T> Cache<T> {
/// Return a new Cache with no value stored in it yet.
pub fn new() -> Self {
// ...
}
/// If the cache has a value stored in it, return a reference to the
/// stored value. Otherwise, compute `f()`, store its output
/// in the cache, and then return a reference to the stored value.
pub fn call<F: FnOnce() -> T>(&self, f: F) -> &T {
// ...
}
}
此处的目标是能够在单个线程中共享对Cache
的多个不可变引用,并且该引用的任何持有者都可以访问该值(如果是第一次则触发计算)。由于我们只需要能够在单个线程中共享Cache
,因此不必Sync
。
这是在幕后使用unsafe
安全地(或至少我认为是安全的)实现API的方法:
use std::cell::UnsafeCell;
pub struct Cache<T> {
value: UnsafeCell<Option<T>>
}
impl<T> Cache<T> {
pub fn new() -> Self {
Cache { value: UnsafeCell::new(None) }
}
pub fn call<F: FnOnce() -> T>(&self, f: F) -> &T {
let ptr = self.value.get();
unsafe {
if (*ptr).is_none() {
let t = f();
// Since `f` potentially could have invoked `call` on this
// same cache, to be safe we must check again that *ptr
// is still None, before setting the value.
if (*ptr).is_none() {
*ptr = Some(t);
}
}
(*ptr).as_ref().unwrap()
}
}
}
是否可以在安全的Rust中实现这种类型(即,不编写我们自己的unsafe
代码,而仅间接依赖标准库中的unsafe
代码)?
很显然,call
方法需要对self
进行突变,这意味着Cache
必须使用某种形式的内部可变性。但是,似乎无法使用Cell
,因为Cell
无法像上述call
的理想API所要求的那样提供对封闭值的引用的检索方法。这是有充分的理由的,因为Cell
提供这样的引用是不合理的,因为它将无法确保在引用的整个生命周期中引用的值都不会发生变化。另一方面,对于Cache
类型,一次调用call
之后,上面的API不提供任何方式来再次更改存储的值,因此可以安全地进行分发寿命可能长于Cache
本身的引用。
如果Cell
无法正常工作,我很好奇Rust标准库是否可以为内部可变性提供其他一些安全的构建基块,以用于实现此Cache
。
RefCell
和Mutex
均未达到此处的目标:
Option<T>
多的数据,并添加不必要的运行时检查。Ref
或MutexGuard
,这不是一回事。仅使用Option
不会提供相同的功能:如果我们共享对Cache
的不可变引用,则此类引用的任何持有者都可以调用call
并获得所需的值(并在此过程中更改Cache
,以便将来的调用将检索相同的值);而共享对Option
的不可变引用,则不可能对Option
进行突变,因此它无法工作。