【问题标题】:How do I write a rust function that can both read and write to a cache?如何编写一个可以读取和写入缓存的 rust 函数?
【发布时间】:2019-04-26 02:22:18
【问题描述】:

原始问题陈述

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

我已经阅读了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!RefCell 等的某种组合。

我有相反的问题。我非常愿意使用struct(事实上,我在我最初的问题陈述中使用了一个),但是使用unsafemutexlazy_static!等等听起来更危险或更复杂我。

该问题的 OP 暗示如果我们可以使用结构,则解决方案将是显而易见的。我想了解这个显而易见的解决方案。

你是不可变的借用它 - 运行 get_for_n 方法,当返回值超出范围时(即在匹配结束时),该方法从 self 借用并释放此借用。您不希望匹配的值因楼梯函数对缓存所做的任何事情而失效。

无论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。这应该是此时发生的唯一借用,所以我希望这个借用会成功,但编译器告诉我:

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

error: aborting due to previous error

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

【问题讨论】:

标签: rust borrow-checker borrowing


【解决方案1】:

明确检查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

【讨论】:

  • 请使用rustfmt 格式化您的代码。您可以在 Playground 右上角的工具下找到它。
  • 或者更好的if let Some (x) = cache.get_for_n (n) { return x; } return stairs (cache, n-1);,它只调用一次get_for_n
  • if let Some (x) = cache.get_for_n (n) { return x; } return stairs (cache, n-1); 也有cannot borrow *cache as mutable because it is also borrowed as immutable 的问题。
【解决方案2】:

将其包装在 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);
    }
}

【讨论】:

    【解决方案3】:

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

    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);
        }
    }
    

    有两个主要变化:

    1. stairs 不再返回 &amp;Vec&lt;Vec&lt;usize&gt;&gt; 而是返回 Vec&lt;Vec&lt;usize&gt;&gt;
    2. Some(result) 的情况下,我们返回result.clone() 而不是result

    2 是 1 的结果,所以让我们关注为什么 1 是必要的以及为什么它可以解决问题。 HashMap 拥有Vec&lt;Vec&lt;usize&gt;&gt;,因此当原始实现返回&amp;Vec&lt;Vec&lt;usize&gt;&gt; 时,它返回的是对HashMap 拥有的内存位置的引用。如果有人要改变HashMap,比如删除一个条目,因为HashMap拥有Vec&lt;Vec&lt;usize&gt;&gt;HashMap会得出结论,释放Vec&lt;Vec&lt;usize&gt;&gt;使用的内存是安全的,我'最终会得到一个悬空的引用。

    我只能返回&amp;Vec&lt;Vec&lt;usize&gt;&gt;,只要我能保证没有人会改变HashMap,只要&amp;Vec&lt;Vec&lt;usize&gt;&gt; 引用存在,并且因为我将&amp;Vec&lt;Vec&lt;usize&gt;&gt; 引用返回给我的调用者,这基本上意味着我需要保证 HashMap 永远不可变(因为我不知道调用者可能会做什么)。

    【讨论】:

      猜你喜欢
      • 2014-06-14
      • 1970-01-01
      • 2018-01-28
      • 2016-04-30
      • 1970-01-01
      • 2015-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多