如何在 Rust 中修复“.. 在循环的上一次迭代中可变地借用”?

Posted

技术标签:

【中文标题】如何在 Rust 中修复“.. 在循环的上一次迭代中可变地借用”?【英文标题】:How to fix ".. was mutably borrowed here in the previous iteration of the loop" in 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。代码还有一些其他的问题,但让我们先拆分问题并解决这个问题。

【问题讨论】:

我已编辑问题以包含完整的错误消息并撤销了我的反对意见。 也相关:Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument? 【参考方案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 并不昂贵,因为它只是在哈希表中查找,并且您知道该值存在。如果我正确理解您的意图,那么繁重的计算无论如何都会进行一次,所以与此相比,这个双重调用可能非常小。我会试着想一个更好的方法来做到这一点。

以上是关于如何在 Rust 中修复“.. 在循环的上一次迭代中可变地借用”?的主要内容,如果未能解决你的问题,请参考以下文章

为什么Rust无法找到wl_display_get_registry?

Rust错误处理

尝试编写命名管道时如何修复“Broken Pipe”错误?

英伟达 CEO 黄仁勋:摩尔定律结束了;苹果新专利:折叠式iPhone可自行修复折痕;Rust 1.64.0 发布|极客头条

如何在 Rust 中进行实时编程?

Rust 学习总结—— 初识 Rust,作为新势力它的前景如何?