如何编写可以同时读取和写入缓存的rust函数?

时间:2019-04-26 02:22:18

标签: rust borrow-checker borrowing

原始问题陈述

我正在尝试编写一个可以从缓存读取和写入的函数,但是我遇到了一个问题,编译器说我不能一成不变地借用缓存。

我已经阅读了https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.htmlhttps://naftuli.wtf/2019/03/20/rust-the-hard-parts/和随机堆栈溢出/ Reddit帖子,但是我看不到如何将他们所说的内容应用于此代码。

use std::collections::HashMap;

struct CacheForMoves {
    set_of_moves: Vec<usize>,
    cache: HashMap<usize, Vec<Vec<usize>>>,
}

impl CacheForMoves {
    fn new(set_of_moves: Vec<usize>) -> CacheForMoves {
        CacheForMoves {
            set_of_moves: set_of_moves,
            cache: HashMap::new(),
        }
    }

    fn get_for_n(&self, n: usize) -> Option<&Vec<Vec<usize>>> {
        self.cache.get(&n)
    }

    fn insert_for_n(&mut self, n: usize, value: Vec<Vec<usize>>) {
        self.cache.insert(n, value);
    }
}

fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
    return match cache.get_for_n(n) {
        Some(result) => result,
        None => stairs(cache, n - 1),
    };
}

fn main() {
    let mut cache = CacheForMoves::new(vec![1, 2]);
    cache.insert_for_n(1, vec![]);
    let result = stairs(&mut cache, 4);
    println!("Found {} possible solutions: ", result.len());
    for solution in result {
        println!("{:?}", solution);
    }
}

这会产生以下编译错误:

error[E0502]: cannot borrow `*cache` as mutable because it is also borrowed as immutable
  --> stairs2.rs:28:18
   |
26 |     return match cache.get_for_n(n) {
   |                  ----- immutable borrow occurs here
27 |         Some(result) => result,
28 |         None => stairs(cache, n - 1)
   |                        ^^^^^ mutable borrow occurs here
29 |     }
30 | }
   | - immutable borrow ends here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.

我不明白为什么它会认为我在第26行上总是借用cache。我的理解是main创建了CacheForMove的实例并拥有该值。它是将值可变地借给stairs函数,因此现在stairs已经可变地借用了该值。我希望能够在该可变借用的引用上调用get_for_ninsert_for_n

我尚不了解的答案

这是How can I mutate other elements of a HashMap when using the entry pattern?的副本吗?

在这个SO问题中,OP希望对缓存中一个键的更新取决于缓存中另一个键的值。我最终确实想这样做,但是在达到这一点之前,我遇到了一个问题。我没有在缓存中查看其他条目以计算“此”条目。该问题的答案表明,他们需要像这样从缓存中拆分插入缓存:

fn compute(cache: &mut HashMap<u32, u32>, input: u32) -> u32 {
    if let Some(entry) = cache.get(&input) {
        return *entry;
    }

    let res = if input > 2 {
        // Trivial placeholder for an expensive computation.
        compute(cache, input - 1) + compute(cache, input - 2)
    } else {
        0
    };
    cache.insert(input, res);
    res
}

但是,我认为我的代码已经无法插入,但是仍然会出现编译错误。

即使我修改了上面的示例以匹配我的API:

fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
    if let Some(entry) = cache.get_for_n(n) {
        return entry;
    }
    let res = stairs(cache, n - 1);
    cache.insert_for_n(n, res.clone());
    res
}

我仍然遇到相同的错误:

error[E0502]: cannot borrow `*cache` as mutable because it is also borrowed as immutable
  --> src/main.rs:29:15
   |
25 | fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
   |                  - let's call the lifetime of this reference `'1`
26 |     if let Some(entry) = cache.get_for_n(n) {
   |                          ----- immutable borrow occurs here
27 |         return entry;
   |                ----- returning this value requires that `*cache` is borrowed for `'1`
28 |     }
29 |     let res = stairs(cache, n - 1);
   |               ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

error[E0499]: cannot borrow `*cache` as mutable more than once at a time
  --> src/main.rs:30:5
   |
25 | fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
   |                  - let's call the lifetime of this reference `'1`
...
29 |     let res = stairs(cache, n - 1);
   |                      ----- first mutable borrow occurs here
30 |     cache.insert_for_n(n, res.clone());
   |     ^^^^^ second mutable borrow occurs here
31 |     res
   |     --- returning this value requires that `*cache` is borrowed for `'1`

error: aborting due to 2 previous errors

Some errors occurred: E0499, E0502.
For more information about an error, try `rustc --explain E0499`.

这是What is the idiomatic way to implement caching on a function that is not a struct method?的副本吗?

在该SO问题中,OP表示他们不愿意使用struct,提供的答案使用unsafemutexlazy_static!,{ {1}},依此类推。

我有相反的问题。我非常愿意使用RefCell(实际上,我在原始问题陈述中使用了一个),但是我使用structunsafemutex等听起来对我来说更危险或更复杂。

该问题的OP意味着,如果我们可以使用结构,则解决方案将是显而易见的。我想学习这种明显的解决方案。

您是不可变地借用它-运行get_for_n方法,该方法从self借用,并在返回值超出范围时(即,在匹配结束时)释放此借用。您不希望楼梯函数对缓存执行任何操作都会使匹配的值无效。

lazy_static!函数不会使用匹配的值。在原始问题陈述所示的实现中:

stairs

我一成不变地借用fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> { return match cache.get_for_n(n) { Some(result) => result, None => stairs(cache, n - 1), }; } 来获取缓存的值。如果有可用值,我将其返回(无需再次递归调用cache)。如果没有任何价值,我希望stairs是可复制的(即,我可以在堆栈中拥有None的副本;我不再需要引用None中的任何数据了) )。此时,我希望能够安全地可变地借用cache来调用cache,因为没有其他借项(可变或不可变的)可以缓存。

要真正理解这一点,请考虑阶梯函数的以下替代实现:

stairs(cache, n-1)

在这里,我使用了一对花括号来限制不可变借位的范围。我执行不可变借位以填充fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> { { let maybe_result = cache.get_for_n(n); if maybe_result.is_some() { return maybe_result.unwrap(); } } return stairs(cache, n - 1); } ,并检查它是否为maybe_result。如果是这样,我将解开内部值并返回它。如果没有,我将终止我的范围,因此所有借用都超出范围,现在无效。没有借贷发生了。

然后,我尝试可变地借用Some以递归调用cache。这应该是此时唯一发生的借用,因此我希望这种借用能够成功,但是编译器告诉我:

stairs

3 个答案:

答案 0 :(得分:0)

显式检查None并在不可变借用起作用之前返回:

fn stairs(cache: &mut CacheForMoves, n: usize) -> &Vec<Vec<usize>> {
    if cache.get_for_n(n).is_none() {
        return stairs(cache, n - 1);
    } else {
        cache.get_for_n(n).unwrap()
    }
}

但是我不想两次调用get_for_n()函数

Rust playground link

答案 1 :(得分:0)

我想我已经弄清楚了,所以记录下我的答案,以防其他人陷入同样的​​问题。编译并运行:

use std::collections::HashMap;

struct CacheForMoves {
    set_of_moves: Vec<usize>,
    cache: HashMap<usize, Vec<Vec<usize>>>
}

impl CacheForMoves {
    fn new(set_of_moves: Vec<usize>) -> CacheForMoves {
        CacheForMoves {
            set_of_moves: set_of_moves,
            cache: HashMap::new()
        }
    }

    fn get_for_n(&self, n: usize) -> Option<&Vec<Vec<usize>>> {
        self.cache.get(&n)
    }

    fn insert_for_n(&mut self, n: usize, value: Vec<Vec<usize>>) {
        self.cache.insert(n, value);
    }
}

fn stairs(cache: &mut CacheForMoves, n: usize) -> Vec<Vec<usize>> {
    return match cache.get_for_n(n) {
        Some(result) => result.clone(),
        None => stairs(cache, n - 1)
    }
}

fn main() {
    let mut cache = CacheForMoves::new(vec![1, 2]);
    cache.insert_for_n(1, vec![]);
    let result = stairs(&mut cache, 4);
    println!("Found {} possible solutions: ", result.len());
    for solution in result {
        println!("{:?}", solution);
    }
}

有2个主要更改:

  1. stairs不再返回&Vec<Vec<usize>>,而是返回Vec<Vec<usize>>
  2. Some(result)情况下,我们返回result.clone()而不是result

2是1的结果,因此让我们集中讨论为什么1是必需的以及为什么它可以解决问题。 HashMap拥有Vec<Vec<usize>>,因此,当原始实现返回&Vec<Vec<usize>>时,它正在返回对HashMap所拥有的存储位置的引用。如果有人要对HashMap进行突变,例如删除一条条目,则由于HashMap拥有Vec<Vec<usize>>HashMap会得出结论,可以安全地释放正在使用的内存由Vec<Vec<usize>>编写,最后我得到一个悬挂的引用。

我只能返回一个&Vec<Vec<usize>>,只要可以保证只要HashMap引用存在,并且由于我要返回{ {1}}是指我的呼叫者,这实际上意味着我需要保证&Vec<Vec<usize>>是永远不变的(因为我不知道呼叫者会做什么)。

答案 2 :(得分:0)

将其包装在Rc中是一种可能的解决方案。

Rc是一个“引用计数”指针,使您可以拥有多个“借用”至同一值。当您调用“克隆”方法时,计数将增加。当值被破坏时,计数将减少。最后,如果引用计数达到0,则指针及其“指向”值将被破坏。 您可能希望在并发环境中使用Arc(基本上是原子引用计数的“指针”),或者如果要创建板条箱,则可以提供更大的灵活性。Arc将与Rc进行相同的工作,除了该计数将自动完成。

那样,您的所有权问题将得到解决,而无需复制整个Vec,而只需复制另一个指向相同“值”的指针即可。

我也用Option::unwrap_or_else代替,这是一种更惯用的方式来解开Option :: Some(T),或者在Option :: None的情况下延迟计算默认值。

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

struct CacheForMoves {
    set_of_moves: Vec<usize>,
    cache: HashMap<usize, Vec<Vec<usize>>>,
}

impl CacheForMoves {
    fn new(set_of_moves: Vec<usize>) -> CacheForMoves {
        CacheForMoves {
            set_of_moves,
            cache: HashMap::new(),
        }
    }

    fn get_for_n(&self, n: usize) -> Option<&Vec<Vec<usize>>> {
        self.cache.get(&n)
    }

    fn insert_for_n(&mut self, n: usize, value: Vec<Vec<usize>>) {
        self.cache.insert(n, value);
    }
}

fn stairs(cache: &Rc<CacheForMoves>, n: usize) -> &Vec<Vec<usize>> {
    cache.get_for_n(n).unwrap_or_else(|| stairs(cache, n - 1))
}

fn main() {
    let mut cache = Rc::new(CacheForMoves::new(vec![1, 2]));
    Rc::get_mut(&mut cache).unwrap().insert_for_n(1, vec![]);
    let result = stairs(&cache, 4);
    println!("Found {} possible solutions: ", result.len());
    for solution in result {
        println!("{:?}", solution);
    }
}