如何使用默认值为HashMap编写安全包装

时间:2016-03-21 21:19:08

标签: rust

我使用默认值实现了HashMap的换行,我想知道它是否安全。

调用get时,可以调整内部映射的大小,以前对值的引用(使用get获得)将指向无效地址。我试图用这样的思想来解决这个问题:"计算机科学中的所有问题都可以通过另一个间接层来解决。 (Butler Lampson)。我想知道这个技巧是否能使这段代码安全。

use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::hash::Hash;

pub struct DefaultHashMap<I: Hash + Eq, T: Clone> {
    default: T,
    map: UnsafeCell<HashMap<I, Box<T>>>,
}

impl<I: Hash + Eq, T: Clone> DefaultHashMap<I, T> {
    pub fn new(default: T) -> Self {
        DefaultHashMap {
            default: default,
            map: UnsafeCell::new(HashMap::new()),
        }
    }

    pub fn get_mut(&mut self, v: I) -> &mut T {
        let m = unsafe { &mut *self.map.get() };
        m.entry(v).or_insert_with(|| Box::new(self.default.clone()))
    }

    pub fn get(&self, v: I) -> &T {
        let m = unsafe { &mut *self.map.get() };
        m.entry(v).or_insert_with(|| Box::new(self.default.clone()))
    }
}

#[test]
fn test() {
    let mut m = DefaultHashMap::new(10usize);
    *m.get_mut(4) = 40;
    let a = m.get(4);
    for i in 1..1024 {
        m.get(i);
    }
    assert_eq!(a, m.get(4));
    assert_eq!(40, *m.get(4));
}

Playground

2 个答案:

答案 0 :(得分:6)

由于你不能 1 改变从get返回的值,我只会在缺少值时返回对默认值的引用。但是,当您调用get_mut时,您可以将该值添加到地图并将引用返回到新添加的值。

这样做的好处是不需要任何unsafe代码。

use std::{borrow::Borrow, collections::HashMap, hash::Hash};

pub struct DefaultHashMap<K, V> {
    default: V,
    map: HashMap<K, V>,
}

impl<K, V> DefaultHashMap<K, V>
where
    K: Hash + Eq,
    V: Clone,
{
    pub fn new(default: V) -> Self {
        DefaultHashMap {
            default,
            map: HashMap::new(),
        }
    }

    pub fn get_mut(&mut self, v: K) -> &mut V {
        let def = &self.default;
        self.map.entry(v).or_insert_with(|| def.clone())
    }

    pub fn get<B>(&self, v: B) -> &V
    where
        B: Borrow<K>,
    {
        self.map.get(v.borrow()).unwrap_or(&self.default)
    }
}

#[test]
fn test() {
    let mut m = DefaultHashMap::new(10usize);
    *m.get_mut(4) = 40;
    let a = m.get(4);
    for i in 1..1024 {
        m.get(i);
    }
    assert_eq!(a, m.get(4));
    assert_eq!(40, *m.get(4));
}

[1]:从技术上讲,如果您的默认值包含内部可变性,则会有不同的行为。在这种情况下,对整个集合应用默认值的修改。如果这是一个问题,您需要使用更接近原始的解决方案。

答案 1 :(得分:1)

我认为这里的借用规则已经涵盖了你。

在这里应用Mutability XOR Aliasing原则,如果你可以保持多个路径到同一个值并同时改变某些东西,那么就会出现不安全的情况。

但是,在你的情况下:

  • 虽然内部mysql_query("UPDATE firstform SET id ='$id', manager_name='$manager', evaluator_name ='$evaluator', outlet_location='$outlet', date='$date', day='$day', time_in ='$timein', time_out='$timeout', overall='$overall', quality='$quality', service='$service', cleanliness='$cleanliness' WHERE $id ='$_SESSION[manager_name]'"); 即使通过对HashMap的别名引用也可以进行变异,但是没有人引用DefaultHashMap本身
  • 虽然有HashMap的引用,但此处不可能删除Box,因此没有悬空指针来自此处
  • 因为你注意保留借用关系(即Box只能通过&mut T获得),所以不可能有&mut DefaultHashMap和别名< / LI>

因此,您的简短示例看起来是安全的,但是要特别警惕不要在&mut T上意外引入允许修改现有值的方法,因为这将是一条很短的道路。悬空指针。

就个人而言,我会用&DefaultHashMap执行所有测试。