There is an interesting stack overflow thread which I attempted to answer a while ago asking about creating a Multi-Key hash map in Rust:
I think this should be possible to made safe in a 0 cost manner but I am also somewhat of a Rust noob, so maybe not. I have an answer in the above thread which is wrong but have tried to fix it, is the following a valid solution to this problem? If not, under what scenario could it break?
use std::cmp::Eq;
use std::hash::Hash;
use std::collections::HashMap;
use std::mem::ManuallyDrop;
pub struct MultiKeyHashMap<K1, K2, V>
where K1: Eq + Hash,
K2: Eq + Hash {
map: HashMap<(K1, K2), V>,
}
impl<K1, K2, V> MultiKeyHashMap<K1, K2, V>
where K1: Eq + Hash,
K2: Eq + Hash {
pub fn insert(&mut self, k1: K1, k2: K2, v: V) -> Option<V> {
self.map.insert((k1, k2), v)
}
pub fn contains_key(&self, k1: &K1, k2: &K2) -> bool {
unsafe {
let k1_ptr = (k1 as *const K1).cast::<ManuallyDrop<K1>>();
let k2_ptr = (k2 as *const K2).cast::<ManuallyDrop<K2>>();
let key = (k1_ptr.read(), k2_ptr.read());
self.map.contains_key((&key as *const(ManuallyDrop<K1>, ManuallyDrop<K2>)).cast::<(K1, K2)>().as_ref().unwrap_unchecked())
}
}
pub fn remove(&mut self, k1: &K1, k2: &K2) -> Option<V> {
unsafe {
let k1_ptr = (k1 as *const K1).cast::<ManuallyDrop<K1>>();
let k2_ptr = (k2 as *const K2).cast::<ManuallyDrop<K2>>();
let key = (k1_ptr.read(), k2_ptr.read());
self.map.remove((&key as *const(ManuallyDrop<K1>, ManuallyDrop<K2>)).cast::<(K1, K2)>().as_ref().unwrap_unchecked())
}
}
pub fn with_capcity(cap: usize) -> Self {
Self{map: HashMap::with_capacity(cap)}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let mut m = MultiKeyHashMap::<String, String, i32>::with_capcity(10);
m.insert("hello".to_owned(), "world".to_owned(), 20);
let (k1, k2) = ("hello".to_owned(), "world".to_owned());
assert!(m.contains_key(&k1, &k2));
assert_eq!(m.remove(&k1, &k2), Some(20));
assert_eq!(m.remove(&k1, &k2), None);
}
}