2016-03-21 14 views
2

HashMapのラップをデフォルト値で実装しました。安全かどうかを知りたいと思います。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

答えて

5

getからの値が変更されていないため、値がない場合はデフォルト値への参照を返すだけです。ただし、get_mutに電話すると、マップに値を追加して、新しく追加された値に参照を戻すことができます。

これは、unsafeコードを必要としないというメリットがあります。

use std::collections::HashMap; 
use std::hash::Hash; 
use std::borrow::Borrow; 

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: 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

私はあなたがここに借入ルールでカバーされていると思います。

ここでMutability XORエイリアシングの原則を適用すると、同じ値に複数のパスを維持して同時に何かを変更することができれば、安全ではなくなります。しかし、あなたの場合は

、:

  • 内部HashMapDefaultHashMapにaliasable参照によって突然変異させることができる一方でBoxへの参照がある一方で、誰もが、HashMap自体
  • への参照を持っていませんここではBoxを消去する可能性はないので、ここからぶら下がったポインタはありません
  • 借用関係を維持するように注意しているので、つまり&mut Tは唯一取得済みです)&mut DefaultHashMapヘクタール、&mut Tとにエイリアスを持つことはできませんそれ

だから、あなたの短い例は見え安全な、しかし誤って変更することができるようになる&DefaultHashMapの方法を導入していないのは特に警戒しますそれが既存の値であれば、これはポインタをぶら下げるための短い道であろう。

個人的には、すべてのテストをOption<String>で実行します。

関連する問題