Skip to main content

surrealdb_types/
hashmap.rs

1//! A concurrent hashmap wrapper around papaya's HashMap.
2//!
3//! This wrapper provides a safe and performant interface to papaya's HashMap
4//! by using `pin()` internally rather than `pin_owned()`. Using `pin_owned()`
5//! can be a significant performance killer, so this wrapper encourages the
6//! correct usage pattern.
7//!
8//! # Performance Note
9//!
10//! Since this wrapper avoids returning `pin` lifetimes by cloning values where
11//! necessary, you should ensure that your value types are cheap to clone. For
12//! types that are expensive to clone, consider wrapping them in an [`Arc`].
13//!
14//! [`Arc`]: std::sync::Arc
15//!
16//! # Example
17//!
18//! ```rust
19//! use surrealdb_types::HashMap;
20//!
21//! let map: HashMap<String, i32> = HashMap::new();
22//! map.insert("key".to_string(), 42);
23//!
24//! if let Some(value) = map.get(&"key".to_string()) {
25//!     assert_eq!(value, 42);
26//! }
27//! ```
28
29use std::collections::hash_map::RandomState;
30use std::{cmp, hash};
31
32use papaya::Equivalent;
33
34/// A concurrent hashmap wrapper around papaya's HashMap.
35///
36/// This wrapper uses `pin()` internally rather than `pin_owned()` to avoid
37/// performance issues. All operations clone values on access, which is
38/// suitable for types that implement `Clone`.
39///
40/// # Type Parameters
41///
42/// * `K` - The key type, must implement `Hash` and `Eq`
43/// * `V` - The value type, must implement `Clone`
44/// * `S` - The hasher type, defaults to `RandomState`
45#[derive(Debug, Clone, Default)]
46pub struct HashMap<K, V, S = RandomState>(papaya::HashMap<K, V, S>)
47where
48	K: hash::Hash + cmp::Eq,
49	V: Clone,
50	S: hash::BuildHasher + Default;
51
52impl<K, V, S> HashMap<K, V, S>
53where
54	K: hash::Hash + cmp::Eq,
55	V: Clone,
56	S: hash::BuildHasher + Default,
57{
58	/// Creates a new empty HashMap.
59	pub fn new() -> Self {
60		Self(Default::default())
61	}
62
63	/// Inserts a key-value pair into the map.
64	///
65	/// If the map already contains this key, the value is updated.
66	pub fn insert(&self, key: K, value: V) {
67		self.0.pin().insert(key, value);
68	}
69
70	/// Returns a clone of the value corresponding to the key.
71	///
72	/// Returns `None` if the key is not present in the map.
73	pub fn get<Q>(&self, key: &Q) -> Option<V>
74	where
75		Q: Equivalent<K> + hash::Hash + ?Sized,
76	{
77		self.0.pin().get(key).cloned()
78	}
79
80	/// Returns `true` if the map contains a value for the specified key.
81	pub fn contains_key<Q>(&self, key: &Q) -> bool
82	where
83		Q: Equivalent<K> + hash::Hash + ?Sized,
84	{
85		self.0.pin().contains_key(key)
86	}
87
88	/// Returns a vector containing clones of all values in the map.
89	pub fn values(&self) -> Vec<V> {
90		self.0.pin().values().cloned().collect()
91	}
92
93	/// Returns a vector containing clones of all key-value pairs in the map.
94	pub fn to_vec(&self) -> Vec<(K, V)>
95	where
96		K: Clone,
97	{
98		let map = self.0.pin();
99		let mut vec = Vec::with_capacity(map.len());
100		for (k, v) in map.iter() {
101			vec.push((k.clone(), v.clone()));
102		}
103		vec
104	}
105
106	/// Removes a key from the map.
107	///
108	/// Traditionally `remove` would return the value but this would require cloning the value,
109	/// which would be an unnecessary expense when you don't need the value. If you need the value,
110	/// use `take` instead.
111	pub fn remove<Q>(&self, key: &Q)
112	where
113		Q: Equivalent<K> + hash::Hash + ?Sized,
114	{
115		self.0.pin().remove(key);
116	}
117
118	/// Removes a key from the map and returns the value.
119	pub fn take<Q>(&self, key: &Q) -> Option<V>
120	where
121		Q: Equivalent<K> + hash::Hash + ?Sized,
122	{
123		self.0.pin().remove(key).cloned()
124	}
125
126	/// Removes all key-value pairs from the map.
127	pub fn clear(&self) {
128		self.0.pin().clear();
129	}
130
131	/// Returns the number of elements in the map.
132	pub fn len(&self) -> usize {
133		self.0.pin().len()
134	}
135
136	/// Returns `true` if the map contains no elements.
137	pub fn is_empty(&self) -> bool {
138		self.0.pin().is_empty()
139	}
140
141	/// Retains only the elements specified by the predicate.
142	///
143	/// In other words, removes all pairs `(k, v)` such that `f(&k, &v)` returns `false`.
144	pub fn retain<F>(&self, f: F)
145	where
146		F: FnMut(&K, &V) -> bool,
147	{
148		self.0.pin().retain(f);
149	}
150}
151
152#[cfg(test)]
153mod tests {
154	use super::*;
155
156	#[test]
157	fn test_basic_operations() {
158		let map: HashMap<String, i32> = HashMap::new();
159
160		// Test insert and get
161		map.insert("key1".to_string(), 1);
162		map.insert("key2".to_string(), 2);
163
164		assert_eq!(map.get(&"key1".to_string()), Some(1));
165		assert_eq!(map.get(&"key2".to_string()), Some(2));
166		assert_eq!(map.get(&"key3".to_string()), None);
167
168		// Test contains_key
169		assert!(map.contains_key(&"key1".to_string()));
170		assert!(!map.contains_key(&"key3".to_string()));
171
172		// Test len
173		assert_eq!(map.len(), 2);
174		assert!(!map.is_empty());
175
176		// Test remove
177		map.remove(&"key1".to_string());
178		assert_eq!(map.get(&"key1".to_string()), None);
179		assert_eq!(map.len(), 1);
180
181		// Test clear
182		map.clear();
183		assert!(map.is_empty());
184	}
185
186	#[test]
187	fn test_values_and_to_vec() {
188		let map: HashMap<String, i32> = HashMap::new();
189		map.insert("a".to_string(), 1);
190		map.insert("b".to_string(), 2);
191		map.insert("c".to_string(), 3);
192
193		let values = map.values();
194		assert_eq!(values.len(), 3);
195
196		let vec = map.to_vec();
197		assert_eq!(vec.len(), 3);
198	}
199
200	#[test]
201	fn test_retain() {
202		let map: HashMap<String, i32> = HashMap::new();
203		map.insert("a".to_string(), 1);
204		map.insert("b".to_string(), 2);
205		map.insert("c".to_string(), 3);
206		map.insert("d".to_string(), 4);
207
208		// Keep only even values
209		map.retain(|_, v| *v % 2 == 0);
210
211		assert_eq!(map.len(), 2);
212		assert!(map.contains_key(&"b".to_string()));
213		assert!(map.contains_key(&"d".to_string()));
214		assert!(!map.contains_key(&"a".to_string()));
215		assert!(!map.contains_key(&"c".to_string()));
216	}
217
218	#[test]
219	fn test_clone() {
220		let map: HashMap<String, i32> = HashMap::new();
221		map.insert("key".to_string(), 42);
222
223		let cloned = map.clone();
224		assert_eq!(cloned.get(&"key".to_string()), Some(42));
225
226		// Modifications to clone don't affect original
227		cloned.insert("key".to_string(), 100);
228		assert_eq!(map.get(&"key".to_string()), Some(42));
229		assert_eq!(cloned.get(&"key".to_string()), Some(100));
230	}
231}