有可能在安全的Rust中具有单线程的零成本备忘录而无可变性吗?

时间:2019-05-20 05:32:22

标签: rust

我对寻找或实现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

RefCellMutex均未达到此处的目标:

  1. 它们不是零成本的:它们涉及到存储比Option<T>多的数据,并添加不必要的运行时检查。
  2. 它们似乎没有提供任何方法来返回具有所需寿命的真实引用-而是只能返回RefMutexGuard,这不是一回事。
  3. li>

仅使用Option不会提供相同的功能:如果我们共享对Cache的不可变引用,则此类引用的任何持有者都可以调用call并获得所需的值(并在此过程中更改Cache,以便将来的调用将检索相同的值);而共享对Option的不可变引用,则不可能对Option进行突变,因此它无法工作。

0 个答案:

没有答案