Bar*_*lly 5 hashmap rust borrow-checker
我正在尝试在 Rust 中实现延迟构造/记忆评估/缓存习语。
有一个外部类型,它有一堆数据和一个访问器方法。访问器方法需要返回缓存的计算(如果有)或计算它并将返回值存储在映射中以备后用。缓存值不需要引用外部值,所以不存在循环引用问题;但它确实需要访问外部值的数据才能构建自己。
这是一个完整的例子,它没有通过 Rust 的借用检查器:
use std::collections::HashMap;
pub struct ContainedThing {
count: usize,
}
impl ContainedThing {
fn create(thing: &Thing) -> ContainedThing {
// create uses an arbitrary number of attributes from Thing
// it doesn't keep any references after returning though
let count = thing.map.len();
ContainedThing { count: count }
}
}
struct Thing {
map: HashMap<i32, ContainedThing>,
}
impl Thing {
pub fn get(&mut self, key: i32) -> &ContainedThing {
self.map
.entry(key)
.or_insert_with(|| ContainedThing::create(&self))
}
}
fn main() {}
Run Code Online (Sandbox Code Playgroud)
具体错误是:
use std::collections::HashMap;
pub struct ContainedThing {
count: usize,
}
impl ContainedThing {
fn create(thing: &Thing) -> ContainedThing {
// create uses an arbitrary number of attributes from Thing
// it doesn't keep any references after returning though
let count = thing.map.len();
ContainedThing { count: count }
}
}
struct Thing {
map: HashMap<i32, ContainedThing>,
}
impl Thing {
pub fn get(&mut self, key: i32) -> &ContainedThing {
self.map
.entry(key)
.or_insert_with(|| ContainedThing::create(&self))
}
}
fn main() {}
Run Code Online (Sandbox Code Playgroud)
我真的很难找到实现这个习语的好方法。我尝试模式匹配get()而不是entry()API的返回值,但仍然违反了借用检查器:match表达式也最终借用了self。
我可以这样重写get:
pub fn get(&mut self, key: i32) -> &ContainedThing {
if !self.map.contains_key(&key) {
let thing = ContainedThing::create(&self);
self.map.insert(key, thing);
}
self.map.get(&key).unwrap()
}
Run Code Online (Sandbox Code Playgroud)
但这非常难看(看看那个unwrap)并且似乎需要比应该更多的查找和副本。理想情况下,我希望
entry(),做得对,应该在未找到时跟踪插入位置。unwrap;没有毫无意义的模式匹配,也就是说。我笨拙的代码是最好的吗?
答案是,这具体取决于您需要在or_insert_with闭包中访问哪个状态。问题是or_insert_with肯定无法访问地图本身,因为入口 api 采用了地图的可变借用。
如果您只需要ContainedThing::create地图的大小,那么您只需要提前计算地图大小即可。
impl Thing {
pub fn get(&mut self, key: i32) -> &ContainedThing {
let map_size = self.map.len();
self.map.entry(&key).or_insert_with(|| {
// The call to entry takes a mutable reference to the map,
// so you cannot borrow map again in here
ContainedThing::create(map_size)
})
}
}
Run Code Online (Sandbox Code Playgroud)
不过,我认为这个问题的精神更多的是关于一般策略,所以让我们假设其中Thing还需要创建一些其他状态ContainedThing。
struct Thing {
map: HashMap<i32, ContainedThing>,
some_other_stuff: AnotherType, //assume that this other state is also required in order to create ContainedThing
}
impl Thing {
pub fn get(&mut self, key: i32) -> &ContainedThing {
//this is the borrow of self
let Thing {
ref mut map,
ref mut some_other_stuff,
} = *self;
let map_size = map.len();
map.entry(key).or_insert_with(|| {
// map.entry now only borrows map instead of self
// Here you're free to borrow any other members of Thing apart from map
ContainedThing::create(map_size, some_other_stuff)
})
}
}
Run Code Online (Sandbox Code Playgroud)
这是否真的比其他手动检查解决方案更干净if self.map.contains_key(&key)还有待争论。不过,解构往往是我所采用的策略,允许借用特定成员而self不是整个结构。
| 归档时间: |
|
| 查看次数: |
352 次 |
| 最近记录: |