如何在 Rust 中懒惰地创建其构造使用 self 的映射条目
How to lazily create map entry whose construction uses self in Rust
我正在尝试在 Rust 中实现惰性构造/记忆化评估/缓存惯用语。
有一个外部类型,它有一堆数据和一个访问器方法。访问器方法需要 return 一个缓存的计算(如果它有一个)或者计算它并将 return 值存储在映射中供以后使用。缓存值不需要引用外部值,所以不存在循环引用问题;但它确实需要访问外部值的数据才能构建自身。
这是一个没有通过 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() {}
具体错误为:
error[E0502]: cannot borrow `self` as immutable because `self.map` is also borrowed as mutable
--> src/main.rs:24:29
|
22 | self.map
| -------- mutable borrow occurs here
23 | .entry(key)
24 | .or_insert_with(|| ContainedThing::create(&self))
| ^^ ---- borrow occurs due to use of `self` in closure
| |
| immutable borrow occurs here
25 | }
| - mutable borrow ends here
我真的很难找到实现这个习惯用法的好方法。我尝试模式匹配 get()
的 return 值而不是 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()
}
但这很丑陋(看看那个 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)
})
}
}
我认为这个问题的精神更多地是关于一般策略,所以让我们假设 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)
})
}
}
这是否真的比手动检查 if self.map.contains_key(&key)
的其他解决方案更干净尚有待商榷。不过,我倾向于采用解构策略,以允许借用 self
的特定成员而不是整个结构。
所以,我想将 Thing
作为参数传递给 ContainedThing::create
的主要动机是使用 Thing
的 API 来帮助构建。然而,事实证明我会希望它被可变地借用,因为我在这里需要递归记忆化构造,这使得它成为一个循环问题。
所以,看来我的分离检查 + 插入逻辑会保留下来。
我正在尝试在 Rust 中实现惰性构造/记忆化评估/缓存惯用语。
有一个外部类型,它有一堆数据和一个访问器方法。访问器方法需要 return 一个缓存的计算(如果它有一个)或者计算它并将 return 值存储在映射中供以后使用。缓存值不需要引用外部值,所以不存在循环引用问题;但它确实需要访问外部值的数据才能构建自身。
这是一个没有通过 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() {}
具体错误为:
error[E0502]: cannot borrow `self` as immutable because `self.map` is also borrowed as mutable
--> src/main.rs:24:29
|
22 | self.map
| -------- mutable borrow occurs here
23 | .entry(key)
24 | .or_insert_with(|| ContainedThing::create(&self))
| ^^ ---- borrow occurs due to use of `self` in closure
| |
| immutable borrow occurs here
25 | }
| - mutable borrow ends here
我真的很难找到实现这个习惯用法的好方法。我尝试模式匹配 get()
的 return 值而不是 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()
}
但这很丑陋(看看那个 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)
})
}
}
我认为这个问题的精神更多地是关于一般策略,所以让我们假设 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)
})
}
}
这是否真的比手动检查 if self.map.contains_key(&key)
的其他解决方案更干净尚有待商榷。不过,我倾向于采用解构策略,以允许借用 self
的特定成员而不是整个结构。
所以,我想将 Thing
作为参数传递给 ContainedThing::create
的主要动机是使用 Thing
的 API 来帮助构建。然而,事实证明我会希望它被可变地借用,因为我在这里需要递归记忆化构造,这使得它成为一个循环问题。
所以,看来我的分离检查 + 插入逻辑会保留下来。