【问题标题】:Implementing only IndexMut without implementing Index只实现 IndexMut 而不实现 Index
【发布时间】:2017-04-22 10:54:37
【问题描述】:

我正在尝试创建一个DefaultHashMap 结构,它基本上是HashMap 的包装器,不同之处在于,当获取不在映射中的键时,默认值被放入该键并返回。

我创建了一个get 和一个get_mut 方法,效果很好。现在我正在尝试实现IndexIndexMut 作为这些方法的包装器。在这里,我遇到了两个问题。

第一个问题是由于get 必须在键不存在时改变结构,它需要一个可变引用。但是Indexindex方法的签名是&self而不是&mut self,所以我无法实现。

这会导致第二个问题,IndexMut 需要 Index 实现。因此,即使IndexMut 的实施没有问题,我也不能这样做,因为Index 无法实施。

第一个问题很烦人但可以理解。对于第二个,我不明白为什么要求甚至存在。我想有办法解决它。现在我正在做以下事情,但我希望有人有更好的解决方案:

impl<K: Eq + Hash, V: Clone> Index<K> for DefaultHashMap<K, V> {
    type Output = V;

    fn index(&self, _: K) -> &V {
        panic!("DefautHashMap doesn't implement indexing without mutating")
    }
}

impl<K: Eq + Hash, V: Clone> IndexMut<K> for DefaultHashMap<K, V> {
    #[inline]
    fn index_mut(&mut self, index: K) -> &mut V {
        self.get_mut(index)
    }
}

【问题讨论】:

标签: rust


【解决方案1】:

首先,我怀疑您的要求“当获取不在地图中的键时,默认值放在该键中”并不完全需要!

考虑一个不可变的访问let foo = default_hash_map[bar] + 123;。除非您打算在映射中使用具有内部可变性的值,否则default_hash_map[bar] 是否实际创建键或仅返回对单个默认值的引用可能无关紧要。

现在,如果您确实需要在访问期间创建新条目,那么有一种方法可以做到这一点。仅允许您添加具有可变访问权限的新条目的借用检查器限制是为了阻止您创建悬挂指针,当您在其中保存引用时修改映射时会发生这种情况。但是,如果您使用具有稳定引用的结构,其中稳定意味着当您在结构中输入新条目时引用不会失效,那么借用检查器试图阻止的问题就会消失。

在 C++ 中,我会考虑使用 deque,标准保证在您向其中添加新条目时不会使其引用无效。不幸的是,Rust 双端队列是不同的(尽管您可能会发现 arena allocator 板条箱具有类似于 C++ 双端队列的属性),因此对于这个示例,我使用了Box。装箱的值单独驻留在堆中,当您将新条目添加到 HashMap 时不会移动。

现在,您的正常访问模式可能是修改新条目,然后访问地图的现有条目。因此,在Index::index 中创建新条目是一个例外,不应减慢地图的其余部分。因此,仅为Index::index 访问支付装箱价格可能是有意义的。为此,我们可能会使用第二种结构,它只保留装箱的 Index::index 值。

知道HashMap&lt;K, Box&lt;V&gt;&gt; 可以插入而不会使现有的V 引用无效,这允许我们将其用作临时缓冲区,保存Index::index 创建的值,直到我们有机会将它们与主@ 同步987654334@.

use std::borrow::Borrow;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Index;
use std::ops::IndexMut;

struct DefaultHashMap<K, V>(HashMap<K, V>, UnsafeCell<HashMap<K, Box<V>>>, V);

impl<K, V> DefaultHashMap<K, V>
    where K: Eq + Hash
{
    fn sync(&mut self) {
        let buf_map = unsafe { &mut *self.1.get() };
        for (k, v) in buf_map.drain() {
            self.0.insert(k, *v);
        }
    }
}

impl<'a, K, V, Q: ?Sized> Index<&'a Q> for DefaultHashMap<K, V>
    where K: Eq + Hash + Clone,
          K: Borrow<Q>,
          K: From<&'a Q>,
          Q: Eq + Hash,
          V: Clone
{
    type Output = V;

    fn index(&self, key: &'a Q) -> &V {
        if let Some(v) = self.0.get(key) {
            v
        } else {
            let buf_map: &mut HashMap<K, Box<V>> = unsafe { &mut *self.1.get() };
            if !buf_map.contains_key(key) {
                buf_map.insert(K::from(key), Box::new(self.2.clone()));
            }
            &*buf_map.get(key).unwrap()
        }
    }
}

impl<'a, K, V, Q: ?Sized> IndexMut<&'a Q> for DefaultHashMap<K, V>
    where K: Eq + Hash + Clone,
          K: Borrow<Q>,
          K: From<&'a Q>,
          Q: Eq + Hash,
          V: Clone
{
    fn index_mut(&mut self, key: &'a Q) -> &mut V {
        self.sync();
        if self.0.contains_key(key) {
            self.0.get_mut(key).unwrap()
        } else {
            self.0.insert(K::from(key), self.2.clone());
            self.0.get_mut(key).unwrap()
        }
    }
}

fn main() {
    {
        let mut dhm = DefaultHashMap::<String, String>(HashMap::new(),
                                                       UnsafeCell::new(HashMap::new()),
                                                       "bar".into());
        for i in 0..10000 {
            dhm[&format!("{}", i % 1000)[..]].push('x')
        }
        println!("{:?}", dhm.0);
    }

    {
        let mut dhm = DefaultHashMap::<String, String>(HashMap::new(),
                                                       UnsafeCell::new(HashMap::new()),
                                                       "bar".into());
        for i in 0..10000 {
            let key = format!("{}", i % 1000);
            assert!(dhm[&key].len() >= 3);
            dhm[&key[..]].push('x');
        }
        println!("{:?}", dhm.0);
    }

    {
        #[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)]
        struct K(u32);
        impl<'a> From<&'a u32> for K {
            fn from(v: &u32) -> K {
                K(*v)
            }
        }
        impl<'a> Borrow<u32> for K {
            fn borrow(&self) -> &u32 {
                &self.0
            }
        }
        let mut dhm = DefaultHashMap::<K, K>(HashMap::new(),
                                             UnsafeCell::new(HashMap::new()),
                                             K::from(&123));
        for i in 0..10000 {
            let key = i % 1000;
            assert!(dhm[&key].0 >= 123);
            dhm[&key].0 += 1;
        }
        println!("{:?}", dhm.0);
    }
}

(playground)

请注意,装箱只会稳定新条目的插入。要删除盒装条目,您仍然需要对 DefaultHashMap 的可变 (&amp;mut self) 访问权限。

【讨论】:

  • 感谢您的精彩而深入的回答!在尝试让 RefCell 工作了几个小时后,我意识到你对引用失效的看法。然后我选择了您的第一个解决方案,只是不将它们插入地图并记录下来。因为在考虑了这个后果之后,它通常确实不会,如果你真的想在访问时插入,你仍然可以直接使用get_mut 方法。如果您有兴趣,可以在这里找到最终的 crate:github.com/JelteF/defaultmap
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-20
  • 1970-01-01
  • 2017-10-27
  • 2017-12-26
  • 1970-01-01
  • 1970-01-01
  • 2016-07-19
相关资源
最近更新 更多