rust - 可以在安全的 Rust 中实现没有可变性的单线程零成本记忆吗?
问题描述
我有兴趣找到或实现一个 Rust 数据结构,它提供了一种零成本的方式来记忆具有任意输出类型的单个计算T
。具体来说,我想要一个泛型类型Cache<T>
,其内部数据占用的空间不超过Option<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 {
// ...
}
}
这里的目标是能够Cache
在单个线程中共享对 a 的多个不可变引用,并且此类引用的任何持有者都能够访问该值(如果是第一次则触发计算)。由于我们只需要能够Cache
在单个线程中共享 a,因此它没有必要是Sync
.
这是一种在底层使用安全(或者至少我认为它是安全的)实现 API 的方法unsafe
:
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
方法需要 mutating self
,这意味着Cache
必须使用某种形式的内部可变性。但是,它似乎无法使用 a Cell
,因为Cell
无法按照上述所需 API 的要求来检索对封闭值的引用call
。这是有充分理由的,因为Cell
提供这样的引用是不合理的,因为它无法确保引用的值在引用的生命周期内不会发生变化。另一方面,对于在被调用一次Cache
之后的类型,call
上面的 API 并没有提供任何方法让存储的值再次发生变异,因此它可以安全地分发一个生命周期可能很长的引用就像它Cache
本身一样。
如果Cell
不能工作,我很好奇 Rust 标准库是否可以为内部可变性提供一些其他安全的构建块,可以用来实现 this Cache
。
既不RefCell
也不Mutex
实现这里的目标:
- 它们不是零成本:它们涉及存储比 an 更多的数据
Option<T>
并添加不必要的运行时检查。 - 它们似乎没有提供任何方法来返回具有我们想要的生命周期的真实引用——相反,我们只能返回 a
Ref
orMutexGuard
which 不是同一件事。
仅使用 anOption
不会提供相同的功能:如果我们共享对 的不可变引用,则Cache
此类引用的任何持有者都可以调用call
并获得所需的值(并Cache
在过程中改变 ,以便将来的调用将检索相同的值);然而,共享对 a 的不可变引用Option
,不可能改变Option
,所以它不能工作。
解决方案
推荐阅读
- javascript - 从 Javascript 函数打开窗口
- firebase - Firebase 查询集合
- typescript - 类型、nuxt 和 typescript 上不存在属性“$router”?
- javascript - TypeError: value.includes 不是使用挂载测试的函数
- c - 如何在 Linux 驱动器上找到分区结束的位置?
- kentico - 我想恢复对页面所做的更改
- npm - 无法安装我在 GCP 中发布到 npm 注册表的作用域包
- php - PHP 和 node.js 在同一台服务器上
- python - 将 pandas df 映射到 JSON Schema
- r - 无法在 R 版本 3.5.2 中安装 tidyverse 包