使用原始指针访问 RefCell<HashMap<T>> 的 &T 是否安全?
Is it safe to use a raw pointer to access the &T of a RefCell<HashMap<T>>?
我有一个内部使用 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)
}
}
Playground with external mutability
现在我需要内部可变性。由于 HashMap
没有实现 Copy
,我的猜测是 RefCell
是要遵循的路径。编写 insert
方法很简单,但我遇到了借用函数的问题。我可以 return 一个 Ref<String>
,但是因为我想缓存结果,所以我写了一个小的 Ref
-wrapper:
struct CacheRef<'a> {
borrow: Ref<'a, HashMap<u32, String>>,
value: &'a String,
}
这将不起作用,因为 value
引用了 borrow
,因此无法构造该结构。我知道引用始终有效:地图无法更改,因为 Ref
locks 地图。使用原始指针而不是引用安全吗?
struct CacheRef<'a> {
borrow: Ref<'a, HashMap<u32, String>>,
value: *const String,
}
我是不是忽略了什么?有更好(或更快)的选择吗?由于运行时开销,我试图避免 RefCell
。
我将忽略您的直接问题,转而使用绝对安全的替代方法:
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>
。这个解决方案的丑陋部分是我们必须在 hashmap 中查找两次,因为我不知道如何使理想的解决方案起作用:
Ref::map(borrow, |hm| {
hm.get(&k) // Returns an `Option`, not a `&...`
})
这 可能 需要通用关联类型 (GAT),即使这样 return 类型也可能是 Ref<Option<T>>
.
我将用不安全的版本补充@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 以通用实现。
正如 Shepmaster 所说,最好尽可能避免不安全。
有多种可能性:
Ref::map
,双 look-up(如 Shepmaster 的回答所示),
Ref::map
具有标记值,
- 克隆 return 值。
就我个人而言,我会首先考虑后者。将 Rc<String>
存储到您的地图中,您的方法可以轻松 return 一个 Option<Rc<String>>
,从而完全回避问题:
fn get(&self, k: u32) -> Option<Rc<String>> {
self.map.borrow().get(&k).cloned()
}
作为奖励,当您使用结果时,您的缓存不再 "locked"。
或者,您可以 work-around 通过使用标记值 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(""))
}
我有一个内部使用 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)
}
}
Playground with external mutability
现在我需要内部可变性。由于 HashMap
没有实现 Copy
,我的猜测是 RefCell
是要遵循的路径。编写 insert
方法很简单,但我遇到了借用函数的问题。我可以 return 一个 Ref<String>
,但是因为我想缓存结果,所以我写了一个小的 Ref
-wrapper:
struct CacheRef<'a> {
borrow: Ref<'a, HashMap<u32, String>>,
value: &'a String,
}
这将不起作用,因为 value
引用了 borrow
,因此无法构造该结构。我知道引用始终有效:地图无法更改,因为 Ref
locks 地图。使用原始指针而不是引用安全吗?
struct CacheRef<'a> {
borrow: Ref<'a, HashMap<u32, String>>,
value: *const String,
}
我是不是忽略了什么?有更好(或更快)的选择吗?由于运行时开销,我试图避免 RefCell
。
我将忽略您的直接问题,转而使用绝对安全的替代方法:
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>
。这个解决方案的丑陋部分是我们必须在 hashmap 中查找两次,因为我不知道如何使理想的解决方案起作用:
Ref::map(borrow, |hm| {
hm.get(&k) // Returns an `Option`, not a `&...`
})
这 可能 需要通用关联类型 (GAT),即使这样 return 类型也可能是 Ref<Option<T>>
.
我将用不安全的版本补充@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 以通用实现。
正如 Shepmaster 所说,最好尽可能避免不安全。
有多种可能性:
Ref::map
,双 look-up(如 Shepmaster 的回答所示),Ref::map
具有标记值,- 克隆 return 值。
就我个人而言,我会首先考虑后者。将 Rc<String>
存储到您的地图中,您的方法可以轻松 return 一个 Option<Rc<String>>
,从而完全回避问题:
fn get(&self, k: u32) -> Option<Rc<String>> {
self.map.borrow().get(&k).cloned()
}
作为奖励,当您使用结果时,您的缓存不再 "locked"。
或者,您可以 work-around 通过使用标记值 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(""))
}