我必须迭代键,按键查找 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 引用视为通用指针(顺便说一句:&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) -> …
对于方法的调用者来说,这更容易使用。
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
}
}
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
错误的重要部分其实就在这里:
返回结果 | ------ 返回该值需要借用
*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 }}
}