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

问题描述 投票:0回答:4

我必须迭代键,按键查找 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`

这是操场上的代码

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
4个回答
6
投票

您无法使用“独占”访问权限进行缓存。你不能将 Rust 引用视为通用指针(顺便说一句:&String

&Box<T>
是双重间接寻址,在 Rust 中非常不惯用。使用
&str
&T
进行临时借用)。

&mut self

不仅意味着可变,还意味着

独占
和可变,因此您的缓存支持仅返回一项,因为它返回的引用必须保持self“锁定”只要它存在。
你需要说服借用检查器,

find

返回的东西不会在你下次调用它时突然消失。目前没有这样的保证,因为该接口不会阻止您调用例如

items.clear()
(借用检查器检查函数的接口允许什么,而不是函数实际做什么)。
您可以通过使用 

Rc

或使用实现

 内存池/竞技场 
的板条箱来做到这一点。 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) -> …

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


5
投票
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
    }
}



0
投票
for

循环转换为

fold
,这让编译器相信
self
不会被可变借用两次。
它无需使用内部可变性或重复的函数调用即可工作;唯一的缺点是,如果提早找到结果,不会短路,而是继续迭代,直到结束。

之前:

for key in vec!["1","2","3"] { let result = self.find(&key.to_owned()); if result.is_some() { return result } }

之后:

vec!["1", "2,", "3"] .iter() .fold(None, |result, key| match result { Some(result) => Some(result), None => self.find(&key.to_string()) })

工作游乐场链接:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=92bc73e4bac556ce163e0790c7d3f154


0
投票

错误的重要部分其实就在这里:

返回结果 | ------ 返回该值需要借用
*self

'1

并不是您在循环内调用
find

,而是您在循环内返回一个引用。

最简单的解决方案是在循环内找到

key

,然后索引并返回outside循环。 两个注意事项:

您需要处理循环返回无效索引/键的可能性。
  • 集合中有一个额外的索引 - 如果多次调用可能会影响性能
  • 在这个具体示例中
find

实际上不需要返回任何内容 - 它只是确保该值被缓存。不知道为什么

find
会在这里返回
None
,但我假设有一些情况。缓存该值并返回
Some
后,您可以直接索引到
items
fn 内部的
it

铁锈游乐场

use std::collections::HashMap; #[derive(Debug)] struct Calculation { _value: Option<i32>, } struct Struct { items: HashMap<String, Box<Calculation>>, } impl Struct { fn cache(&mut self, key: &String) -> Option<()> { if *key == String::from("1") { return None; } match self.items.get(key) { None => { let calculation = Calculation { _value: None }; self.items.insert(key.clone(), Box::new(calculation)); Some(()) } Some(_) => Some(()), } } fn it(&mut self) -> Option<&Box<Calculation>> { let mut result: Option<String> = None; for key in vec!["1", "2", "3"] { if let Some(_) = self.cache(&String::from(key)) { result = Some(String::from(key)); break; } } if let Some(key) = result { return Some(self.items.get(&key).unwrap()); } None } } fn main() { let mut s = Struct { items: HashMap::new(), }; println!("{:?}", s.it()); // Some(Calculation { value: None }) println!("{:?}", s.items); // {"2": Calculation { value: None }} }

© www.soinside.com 2019 - 2024. All rights reserved.