Rust 在安全防锈的情况下,有可能实现单线程零成本记忆,而不存在易变性?
我感兴趣的是找到或实现一个Rust数据结构,该结构提供了一种零成本的方法,可以用任意输出类型Rust 在安全防锈的情况下,有可能实现单线程零成本记忆,而不存在易变性?,rust,Rust,我感兴趣的是找到或实现一个Rust数据结构,该结构提供了一种零成本的方法,可以用任意输出类型T记忆单个计算。具体来说,我想要一个通用类型的缓存,其内部数据占用的空间不超过选项,具有以下基本API: impl<T> Cache<T> { /// Return a new Cache with no value stored in it yet. pub fn new() -> Self { // ... } /// I
T
记忆单个计算。具体来说,我想要一个通用类型的缓存
,其内部数据占用的空间不超过选项
,具有以下基本API:
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 {
// ...
}
}
是否可以在safe Rust中实现这种类型(即,不编写我们自己的unsafe
代码,仅间接依赖标准库中的unsafe
代码)
显然,call
方法需要变异self
,这意味着Cache
必须使用某种形式的内部可变性。但是,似乎不可能使用单元格
,因为单元格
无法按照上面调用
的所需API的要求检索对封闭值的引用。这是一个很好的理由,Cell
提供这样的引用是不合理的,因为它无法确保引用值在引用的生命周期内不会发生变化。另一方面,对于缓存
类型,在调用一次调用
后,上面的API不提供任何方式让存储值再次变异,因此它可以安全地发出一个与缓存
本身的生命周期一样长的引用
如果Cell
无法工作,我很好奇Rust标准库是否会为内部可变性提供一些其他安全的构建块,这些构建块可用于实现此缓存
无论是RefCell
还是Mutex
都不能实现以下目标:
它们不是零成本:它们需要存储比选项
更多的数据,并添加不必要的运行时检查
它们似乎没有提供任何方法来返回具有我们想要的生存期的真实引用——相反,我们只能返回一个Ref
或MutexGuard
,这不是一回事
仅使用选项
不会提供相同的功能:如果我们共享对缓存
的不可变引用,则此类引用的任何持有者都可以调用调用
并获得所需的值(并在过程中对缓存
进行变异,以便将来的调用将检索相同的值);然而,共享对选项
的不可变引用时,不可能对选项
进行变异,因此无法工作 怎么办?请您在问题中编辑一下,您不是在寻找Sync
解决方案吗?这也是我的第一个问题,而且可能是唯一可能解决你任务的办法。@BrentKerby:哦,我相信它能;但你需要某种“同步选项”。实际上,这引发了另一个问题:f
被多次调用是否重要?值得注意的是,如果对f()
的调用以某种方式调用了Cache::call
,从而触发另一次执行f()
?是的,我现在确信我最初的实现是不可靠的,因为它可能导致未定义的行为,由call
返回的不可变引用引用的值仍然可能发生from变异。我现在对它进行了修改,以防止f()
本身可能在同一缓存上调用call
。这东西肯定很难弄对!为了它的价值:
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()
}
}
}