Deploying IndexMut Only Without Index Deployment

I am trying to create a DefaultHashMap structure, which basically is a wrapper around the HashMap , with the difference that upon receiving a key that is not on the map, the default value is placed in this key and returned.

I made a get and get_mut method and it works fine. Now I'm trying to implement Index and IndexMut as wrappers around these methods. Here I ran into two problems.

The first problem is that get must mutate the structure when the key is missing, this requires a variable reference. However, the signature for the Index Index method has &self instead of &mut self , so I cannot implement it.

This causes a second problem, IndexMut requires an implementation of Index . So even if IndexMut does not implement any problems, I cannot do this because Index cannot be implemented.

The first problem is annoying, but understandable. For the second, I do not understand why this requirement exists even there. I would like to have a way around this. Now I am doing the following, but I hope someone has a better solution:

 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) } } 
+5
source share
1 answer

Firstly, I suspect that your requirement "when receiving a key that is not on the card, by default in this key" is not required!

Consider indispensable access let foo = default_hash_map[bar] + 123; . If you are not going to use values ​​with internal volatility with the map, this may not be relevant if default_hash_map[bar] actually creates a key or simply returns a link to a single default value .

Now, if you really need to create new records during access, then there is a way to do this. The borrower check restriction, which allows you to add new records with variable access, is here to stop you from creating dangling pointers that will occur whenever you change the map while holding links there. But if you used a structure with stable links, where stable means that the links are not invalid when entering new entries into the structure, then the problem that checks are trying to prevent will disappear.

In C ++, I would think of using deque , which is guaranteed by the standard, so as not to strip its links when you add new entries to it. Unfortunately, Rust deques are different (although you can probably find boxes for arena distributors with properties similar to C ++ deque), and so I use Box for this example. The values ​​in the box are separately on the heap and do not move when new entries are added to the HashMap .

Now your regular access template is likely to modify the new entries and then access the existing map entries. Thus, creating new entries in Index::index is an exception and should not slow down the rest of the map. Therefore, it makes sense to pay the boxing price only for access to Index::index . To do this, we can use the second structure, which stores only the values ​​in the Index::index box.

Knowing that HashMap<K, Box<V>> can be inserted without canceling the existing V referees, allows us to use it as a temporary buffer, maintaining the Index::index -created values ​​until we get the opportunity to synchronize them with the main HashMap .

 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 )

Please note that boxing only stabilizes the insertion of new records. To remove pasted records, you still need a modified ( &mut self ) access to DefaultHashMap .

+5
source

Source: https://habr.com/ru/post/1267047/


All Articles