【问题标题】:How to fix ".. was mutably borrowed here in the previous iteration of the loop" in Rust?如何在 Rust 中修复“.. 在循环的上一次迭代中可变地借用”?
【发布时间】:2021-05-15 22:55:41
【问题描述】:

我必须对键进行迭代,通过键在 HashMap 中找到值,可能在找到的结构中作为值进行一些繁重的计算(惰性 => 改变结构)并将其缓存在 Rust 中返回。

我收到以下错误消息:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:25:26
   |
23 |     fn it(&mut self) -> Option<&Box<Calculation>> {
   |           - let's call the lifetime of this reference `'1`
24 |         for key in vec!["1","2","3"] {
25 |             let result = self.find(&key.to_owned());
   |                          ^^^^ `*self` was mutably borrowed here in the previous iteration of the loop
...
28 |                 return result
   |                        ------ returning this value requires that `*self` is borrowed for `'1`

这里是code in playground

use std::collections::HashMap;

struct Calculation {
    value: Option<i32>
}

struct Struct {
    items: HashMap<String, Box<Calculation>> // cache
}

impl Struct {
    fn find(&mut self, key: &String) -> Option<&Box<Calculation>> {
        None // find, create, and/or calculate items
    }

    fn it(&mut self) -> Option<&Box<Calculation>> {
        for key in vec!["1","2","3"] {
            let result = self.find(&key.to_owned());
            if result.is_some() {
                return result
            }
        }
        None
    }
}
  • 我无法避免循环,因为我必须检查多个键
  • 我必须让它可变(self 和结构),因为可能的计算会改变它

关于如何改变设计(因为 Rust 迫使以一种有意义的不同方式思考)或解决它的任何建议?

PS。代码还有一些其他的问题,但让我们先拆分问题并解决这个问题。

【问题讨论】:

标签: rust ownership


【解决方案1】:

您不能使用 独占 访问权限进行缓存。您不能将 Rust 引用视为通用指针(顺便说一句:&amp;String&amp;Box&lt;T&gt; 是双重间接,并且在 Rust 中非常单一。使用 &amp;str&amp;T 进行临时借用)。

&amp;mut self 不仅意味着可变,而且独占和可变,因此您的缓存支持仅返回一项,因为它返回的引用必须保持self“锁定”只要它存在。

你需要让借用检查器相信 find 返回的东西不会在你下次调用它时突然消失。目前没有这样的保证,因为接口不会阻止你调用例如items.clear()(借用检查器检查函数的接口允许什么,而不是函数实际执行什么)。

您可以通过使用Rc 或使用实现a memory pool/arena 的板条箱来做到这一点。

struct Struct {
   items: HashMap<String, Rc<Calculation>>,
}

fn find(&mut self, key: &str) -> Rc<Calculation> 

这样,如果您克隆 Rc,它会在需要的时间内存活,与缓存无关。

您还可以通过内部可变性使其更好。

struct Struct {
   items: RefCell<HashMap<…
}

这将允许您的记忆 find 方法使用共享借用而不是独占借用:

fn find(&self, key: &str) -> …

这对于方法的调用者来说更容易使用。

【讨论】:

  • 为了修改 Calculation (Rc::get_mut()) Rc strong_count 必须为 = 0,但它可以在某处使用。基本上这会锁定突变,直到它被读取或保存在某个地方。因此,如果我理解正确,仅使用 RC 并没有帮助(而不是编译问题,我们有相同的运行时问题)
  • 如果您需要共享可变性(这会导致缓存条目也发生突变),请使用Rc&lt;RefCell&lt;Calculation&gt;&gt;
【解决方案2】:

可能不是最干净的方法,但它可以编译。我们的想法是不要将找到的值存储在临时结果中,以避免出现别名:如果存储结果,self 会一直被借用。

impl Struct {

    fn find(&mut self, key: &String) -> Option<&Box<Calculation>> {
        None
    }

    fn it(&mut self) -> Option<&Box<Calculation>> {
        for key in vec!["1","2","3"] {
            if self.find(&key.to_owned()).is_some() {
                return self.find(&key.to_owned());
            }
        }
        None
    }
}

【讨论】:

  • 这似乎对性能不利:find 被调用了两次。应该有“正道”
  • 我同意你的观点,这可能会更好。另一方面,对于您的具体情况,find 并不昂贵,因为它只是在哈希表中查找,并且您知道该值存在。如果我正确理解您的意图,那么繁重的计算无论如何都会进行一次,所以与此相比,这个双重调用可能非常小。我会试着想一个更好的方法来做到这一点。
猜你喜欢
  • 2023-01-10
  • 2021-05-15
  • 1970-01-01
  • 2022-08-17
  • 1970-01-01
  • 2018-03-05
  • 1970-01-01
  • 2020-12-05
  • 1970-01-01
相关资源
最近更新 更多