我有一个类似缓存的结构,它在内部使用HashMap
:
impl Cache {
fn insert(&mut self, k: u32, v: String) {
self.map.insert(k, v);
}
fn borrow(&self, k: u32) -> Option<&String> {
self.map.get(&k)
}
}
具有外部可变性的游乐场
现在我需要内部可变性。由于HashMap
没有实现Copy
,我的猜测是RefCell
是要遵循的道路。编写insert
方法很简单,但我遇到了借用函数的问题。我可以返回一个Ref<String>
,但由于我想缓存结果,所以我写了一个小Ref
-wrapper:
struct CacheRef<'a> {
borrow: Ref<'a, HashMap<u32, String>>,
value: &'a String,
}
这是行不通的,因为value
引用borrow
,所以结构不能构造。我知道引用始终有效:地图不能变异,因为Ref
会锁定地图。使用原始指针而不是引用是否安全?
struct CacheRef<'a> {
borrow: Ref<'a, HashMap<u32, String>>,
value: *const String,
}
我在这里忽略了什么吗?有更好(或更快(的选择吗?由于运行时开销,我试图避免RefCell
。
具有内部可变性的游乐场
我将用不安全的版本补充@Shepmaster安全但效率不高的答案。为此,我们将在实用程序函数中打包一些不安全的代码。
fn map_option<'a, T, F, U>(r: Ref<'a, T>, f: F) -> Option<Ref<'a, U>>
where
F: FnOnce(&'a T) -> Option<&'a U>
{
let stolen = r.deref() as *const T;
let ur = f(unsafe { &*stolen }).map(|sr| sr as *const U);
match ur {
Some(u) => Some(Ref::map(r, |_| unsafe { &*u })),
None => None
}
}
我很确定这段代码是正确的。尽管编译器对生命周期相当不满意,但它们还是解决了。我们只需要注入一些原始指针来使编译器闭嘴。
有了这个,borrow
的实现变得微不足道:
fn borrow<'a>(&'a self, k: u32) -> Option<Ref<'a, String>> {
map_option(self.map.borrow(), |m| m.get(&k))
}
更新了游乐场链接
实用程序函数仅适用于Option<&T>
。其他容器(如Result
(将需要自己的修改副本,否则 GAT 或 HKT 才能通用实现。
我将忽略您的直接问题,以支持绝对安全的替代方案:
impl Cache {
fn insert(&self, k: u32, v: String) {
self.map.borrow_mut().insert(k, v);
}
fn borrow<'a>(&'a self, k: u32) -> Option<Ref<'a, String>> {
let borrow = self.map.borrow();
if borrow.contains_key(&k) {
Some(Ref::map(borrow, |hm| {
hm.get(&k).unwrap()
}))
} else {
None
}
}
}
Ref::map
允许您获取Ref<'a, T>
并将其转换为Ref<'a, U>
。这个解决方案的丑陋部分是我们必须在哈希图中查找两次,因为我无法弄清楚如何使理想的解决方案工作:
Ref::map(borrow, |hm| {
hm.get(&k) // Returns an `Option`, not a `&...`
})
这可能需要泛型关联类型 (GAT(,即使这样,返回类型也可能是Ref<Option<T>>
。
正如Shepmaster所提到的,最好尽可能避免不安全。
有多种可能性:
-
Ref::map
,双重查找(如牧羊人的回答所示(, - 具有哨兵值的
Ref::map
, - 克隆返回值。
就个人而言,我会首先考虑后者。将Rc<String>
存储到地图中,您的方法可以轻松返回一个完全回避问题的Option<Rc<String>>
:
fn get(&self, k: u32) -> Option<Rc<String>> {
self.map.borrow().get(&k).cloned()
}
作为奖励,当您使用结果时,您的缓存不再被"锁定"。
或者,您也可以通过使用哨兵值来解决Ref::map
不喜欢Option
的事实:
fn borrow<'a>(&'a self, k: u32) -> Ref<'a, str> {
let borrow = self.map.borrow();
Ref::map(borrow, |map| map.get(&k).map(|s| &s[..]).unwrap_or(""))
}