Rust 中的安全缓存无需重新计数

use*_*156 4 caching rust

我正在尝试编写一个返回某种列表的函数。由于这个函数是纯函数,并且我可能会多次调用它,因此我想缓存结果。为了避免分配,我想返回所有都指向同一内存位置的引用,这样我只需要在每次函数的唯一调用时执行一次分配。

原则上,我可以将每个结果作为向量存储在缓存中,并返回对结果向量切片的引用。这看起来不安全,但(我认为)事实并非如此。如果您从不删除或修改缓存的元素,那么您的切片引用就是安全的。当您添加到缓存时,矢量可能会移动,但它们的切片不应移动。

显然借用检查器不会接受这个解决方案。就其而言,当函数返回时,缓存被借用,并且您不能再次调用它,因为这需要可变借用。

到目前为止,我拥有的最佳解决方案是在此处的操场上,包括如下:

use std::collections::HashMap;
use std::rc::Rc;

fn n_trues(n: usize, cache: &mut HashMap<usize, Rc<[bool]>>) -> Rc<[bool]> {
    cache
        .entry(n)
        .or_insert_with(|| vec![true; n].into())
        .clone()
}

fn main() {
    let mut cache = HashMap::new();
    let zero = n_trues(0, &mut cache);
    let one = n_trues(1, &mut cache);
    let other_zero = n_trues(0, &mut cache);
    assert!(Rc::ptr_eq(&zero, &other_zero));
    for x in [zero, one, other_zero].iter() {
        println!("{:?}", x);
    }
}
Run Code Online (Sandbox Code Playgroud)

满足Rc借用检查器:从缓存中删除不会使其他引用无效,并且您不能改变Rc. 然而,实际的引用计数是多余的:我们确保缓存仅在程序结束时超出范围,因此在此之前缓存的值不会被释放。这是借用检查器可以检查的事情:它可以确保缓存的寿命比所有结果都长。

有没有什么方法可以消除引用计数而不诉诸不安全代码?

She*_*ter 5

如果您从不删除或修改缓存的元素,那么您的切片引用就是安全的。当您添加到缓存时,矢量可能会移动,但它们的切片不应移动。

你的分析对我来说似乎是正确的。

需要明确的是,这意味着不能从删除任何内容HashMap,也不能从Vec内部添加或删除任何内容HashMap。执行任何操作都可能导致内存被重新分配,从而使引用无效。它也只能起作用,因为它Vec向堆引入了一定程度的间接性,这样就可以保持稳定。

这看起来不安全,但(我认为)事实并非如此。

安全在 Rust 中具有非常特殊的含义。具体来说,某些类型的行为是任何 Rust 代码中都不允许的。即使使用unsafe块,您也不允许调用该行为。

安全 Rust 是所有 Rust 的子集,编译器可以保证它永远不会执行任何上述问题。值得注意的是,这意味着某些类型的代码不会生成未定义的行为,但编译器无法保证。这就是unsafe代码发挥作用的地方。程序员(而不是编译器)需要验证保证不会被破坏,就像在 C 或 C++ 中一样。

不安全的 Rust 代码并不,它只是比安全的 Rust 代码需要更多的程序员审查。这仍然比对所有代码应用这种级别的审查(同样,就像在 C 或 C++ 中那样)要好得多。

有没有什么方法可以消除引用计数而不诉诸不安全代码?

我什么都不知道。你能做的就是寻找一个已经为你做这件事的箱子。

我喜欢使用的一个是typed-arena,它允许分配许多具有相同时间长度的东西:

extern crate typed_arena;

use typed_arena::Arena;

use std::collections::HashMap;

fn n_trues<'a>(n: usize, slab: &'a Arena<Vec<bool>>, cache: &mut HashMap<usize, &'a [bool]>) -> &'a [bool] {
    cache
        .entry(n)
        .or_insert_with(|| slab.alloc(vec![true; n]))
        .clone()
}

fn main() {
    let slab = Arena::new();
    let mut cache = HashMap::new();
    let zero = n_trues(0, &slab, &mut cache);
    let one = n_trues(1, &slab, &mut cache);
    let other_zero = n_trues(0, &slab, &mut cache);
    assert_eq!(zero.as_ptr(), other_zero.as_ptr());
    for x in [zero, one, other_zero].iter() {
        println!("{:?}", x);
    }
}
Run Code Online (Sandbox Code Playgroud)