triple_r/hashmap.rs
1use crate::ReuseCastInto;
2use std::{
3 cell::UnsafeCell,
4 collections::{hash_map::RandomState, HashMap},
5 hash::BuildHasher,
6 marker::PhantomData,
7 ops::{Deref, DerefMut},
8};
9
10/// A wrapper around `HashMap` that allows for reusing its allocation across
11/// different key and value types, provided they are compatible.
12///
13/// `ReusableHashMap` is designed for performance-critical scenarios where heap
14/// allocations are a bottleneck. By recycling the `HashMap`'s allocation, you
15/// can avoid the overhead of deallocating and reallocating memory between
16/// operations.
17///
18/// The core of this mechanism is the `recycle` method, which returns a
19/// [`ReusableHashMapGuard`]. This guard provides temporary, exclusive access to the
20/// underlying `HashMap`. When the guard is dropped, the map is cleared, but its
21/// allocation is preserved and ready for the next use.
22///
23/// The type parameters `K` and `V` must be `'static` to ensure that the
24/// `ReusableHashMap` can hold any value type. The actual lifetime and type
25/// constraints are enforced on the `recycle` method.
26///
27/// # Safety
28///
29/// This struct uses `UnsafeCell` to hold the `HashMap`, which allows for mutating
30/// the map's contents even with a shared reference. The safety of this operation
31/// is guaranteed by the `recycle` method, which requires a mutable reference to
32/// `self`, ensuring that only one `ReusableHashMapGuard` can exist at a time.
33/// This prevents concurrent access and data races.
34///
35/// # Examples
36///
37/// Basic reuse with the same types:
38///
39/// ```
40/// use triple_r::ReusableHashMap;
41///
42/// let mut reusable_map = ReusableHashMap::<String, i32>::default();
43///
44/// // First use
45/// {
46/// let mut map_guard = reusable_map.recycle::<String, i32>();
47/// map_guard.insert("one".to_string(), 1);
48/// assert_eq!(map_guard.get("one"), Some(&1));
49/// } // map_guard is dropped here, and the map is cleared.
50///
51/// // The map is now empty, but its allocation is ready for reuse.
52/// // Second use
53/// {
54/// let mut map_guard = reusable_map.recycle::<String, i32>();
55/// assert!(map_guard.is_empty());
56/// map_guard.insert("two".to_string(), 2);
57/// assert_eq!(map_guard.len(), 1);
58/// }
59/// ```
60///
61/// Reusing a map with different lifetimes:
62///
63/// ```
64/// use triple_r::{ReusableHashMap, ReuseCastInto};
65///
66/// // This trait implementation is necessary to allow the cast.
67/// // It is already implemented for references in the crate.
68/// // unsafe impl<'l1, 'l2, T: ?Sized> ReuseCastInto<&'l2 T> for &'l1 T {}
69///
70/// let mut reusable_map = ReusableHashMap::<&'static str, i32>::default();
71///
72/// {
73/// let key = "temporary";
74/// let mut map_guard = reusable_map.recycle::<&str, i32>();
75/// map_guard.insert(key, 100);
76/// assert_eq!(map_guard.get(key), Some(&100));
77/// } // The guard is dropped, and `key` can no longer be accessed through it.
78/// ```
79#[derive(Debug)]
80pub struct ReusableHashMap<K: 'static, V: 'static, S: 'static + BuildHasher + Default = RandomState>
81{
82 inner: UnsafeCell<HashMap<K, V, S>>,
83}
84
85// The `ReusableHashMap` is safe to send across threads if its contents are `Send`.
86// The `UnsafeCell` contains the data, and if the data `K`, `V`, `S` is `Send`,
87// then the entire `ReusableHashMap` can be safely sent to another thread.
88unsafe impl<K: Send, V: Send, S: 'static + Send + BuildHasher + Default> Send
89 for ReusableHashMap<K, V, S>
90{
91}
92// The `ReusableHashMap` is safe to share across threads if its contents are `Send`.
93// This is because the `recycle` method, which provides access to the inner `HashMap`,
94// requires a mutable borrow `&mut self`. This ensures that only one thread can
95// access the map at a time when not protected by a `Mutex` or other lock.
96// When you wrap `ReusableHashMap` in a `Mutex`, you can safely share it and
97// call `recycle` from multiple threads, as the lock serializes access.
98unsafe impl<K: Send, V: Send, S: 'static + Send + BuildHasher + Default> Sync
99 for ReusableHashMap<K, V, S>
100{
101}
102
103impl<K: 'static, V: 'static, S: 'static + BuildHasher + Default> Default
104 for ReusableHashMap<K, V, S>
105{
106 /// Creates a new, empty `ReusableHashMap` with the default hasher.
107 ///
108 /// # Examples
109 ///
110 /// ```
111 /// use triple_r::ReusableHashMap;
112 ///
113 /// let mut map = ReusableHashMap::<String, i32>::default();
114 /// assert_eq!(map.recycle::<String, i32>().capacity(), 0);
115 /// ```
116 fn default() -> Self {
117 Self {
118 inner: UnsafeCell::new(HashMap::default()),
119 }
120 }
121}
122
123/// A RAII guard that provides temporary, exclusive access to a `HashMap`
124/// retrieved from a [`ReusableHashMap`].
125///
126/// This guard is created by the [`ReusableHashMap::recycle`] method. It holds a
127/// mutable pointer to the underlying `HashMap`, allowing it to be read from and
128/// written to.
129///
130/// When `ReusableHashMapGuard` is dropped, it clears the `HashMap`, ensuring
131/// that its contents are not carried over to the next use. However, the memory
132/// allocation of the `HashMap` is preserved, which is the key to its efficiency.
133///
134/// The lifetime `'parent` ensures that the guard cannot outlive the `ReusableHashMap`
135/// from which it was borrowed.
136///
137/// # Type Parameters
138///
139/// - `'parent`: The lifetime of the mutable borrow of the parent [`ReusableHashMap`].
140/// - `K1`, `V1`: The original key and value types of the `ReusableHashMap`.
141/// - `K2`, `V2`: The new key and value types for the current use.
142/// - `S`: The `BuildHasher` used by the `HashMap`.
143pub struct ReusableHashMapGuard<'parent, K1, V1, K2, V2, S>
144where
145 K1: 'static,
146 V1: 'static,
147 S: 'static + BuildHasher + Default,
148{
149 inner: *mut HashMap<K2, V2, S>,
150 _parent: PhantomData<&'parent mut ReusableHashMap<K1, V1, S>>,
151}
152
153impl<'parent, K1, V1, K2, V2, S> Deref for ReusableHashMapGuard<'parent, K1, V1, K2, V2, S>
154where
155 K1: 'static,
156 V1: 'static,
157 S: 'static + BuildHasher + Default,
158{
159 type Target = HashMap<K2, V2, S>;
160
161 /// Provides immutable access to the underlying `HashMap`.
162 ///
163 /// This allows you to call any of `HashMap`'s immutable methods directly
164 /// on the guard.
165 ///
166 /// # Safety
167 ///
168 /// `self.inner` is a valid pointer for the lifetime `'parent`. This is
169 /// enforced by the `_parent` phantom data and the signature of the `recycle`
170 /// method. The parent `ReusableHashMap` is mutably borrowed for `'parent`,
171 /// so no other access is possible during the guard's lifetime.
172 fn deref(&self) -> &Self::Target {
173 // SAFETY: `self.inner` is a valid pointer for the lifetime `'parent`.
174 // This is enforced by `_parent` and the `reuse` method signature.
175 // The parent `ReusableHashMap` is mutably borrowed for `'parent`,
176 // so no other access is possible.
177 unsafe { &*self.inner }
178 }
179}
180
181impl<'parent, K1, V1, K2, V2, S> DerefMut for ReusableHashMapGuard<'parent, K1, V1, K2, V2, S>
182where
183 K1: 'static,
184 V1: 'static,
185 S: 'static + BuildHasher + Default,
186{
187 /// Provides mutable access to the underlying `HashMap`.
188 ///
189 /// This allows you to call any of `HashMap`'s mutable methods directly
190 /// on the guard, such as `insert` or `clear`.
191 ///
192 /// # Safety
193 ///
194 /// The same safety guarantees as `deref` apply. Mutable access is safe
195 /// because the `'parent` mutable borrow on the original map prevents any
196 /// other code from accessing it.
197 fn deref_mut(&mut self) -> &mut Self::Target {
198 // SAFETY: The same guarantees as `deref` apply. We provide mutable access,
199 // which is safe because the `'parent` mutable borrow on the original map
200 // prevents any other code from accessing it.
201 unsafe { &mut *self.inner }
202 }
203}
204
205impl<K1, V1, S> ReusableHashMap<K1, V1, S>
206where
207 K1: 'static,
208 V1: 'static,
209 S: 'static + BuildHasher + Default,
210{
211 /// Borrows the `HashMap` for temporary use, returning a guard that allows
212 /// access to it.
213 ///
214 /// This method allows you to "cast" the key and value types of the `HashMap`
215 /// to new types (`K2`, `V2`), provided that the original types (`K1`, `V1`)
216 /// implement `ReuseCastInto<K2>` and `ReuseCastInto<V2>` respectively.
217 ///
218 /// Taking `&mut self` as a parameter is crucial for safety, as it ensures
219 /// that only one `ReusableHashMapGuard` can exist at any given time for a
220 /// specific `ReusableHashMap`. This prevents data races.
221 ///
222 /// When the returned guard is dropped, the map is cleared, but its memory
223 /// allocation is preserved for future use.
224 ///
225 /// # Type Parameters
226 ///
227 /// - `'parent`: The lifetime of the returned guard, tied to the mutable
228 /// borrow of `self`.
229 /// - `K2`, `V2`: The new key and value types to use for the `HashMap`.
230 ///
231 /// # Safety
232 ///
233 /// This method performs a transmutation of the `HashMap`'s generic types.
234 /// It is safe because:
235 /// 1. The `ReuseCastInto` trait bounds ensure that the type transmutation
236 /// is valid (e.g., `&'static str` to `&'a str`).
237 /// 2. The borrow checker ensures the returned guard does not outlive `self`.
238 /// 3. The `&mut self` receiver prevents multiple guards from being created
239 /// simultaneously.
240 ///
241 /// # Examples
242 ///
243 /// ```
244 /// use triple_r::ReusableHashMap;
245 ///
246 /// let mut map = ReusableHashMap::<String, String>::default();
247 /// {
248 /// let mut guard = map.recycle::<String, String>();
249 /// guard.insert("key".to_string(), "value".to_string());
250 /// assert_eq!(guard.len(), 1);
251 /// } // Guard is dropped, map is cleared.
252 ///
253 /// assert!(map.recycle::<String, String>().is_empty());
254 /// ```
255 pub fn recycle<'parent, K2, V2>(
256 &'parent mut self,
257 ) -> ReusableHashMapGuard<'parent, K1, V1, K2, V2, S>
258 where
259 K1: ReuseCastInto<K2>,
260 V1: ReuseCastInto<V2>,
261 {
262 // SAFETY: We use `get()` to obtain a raw pointer to the hash map.
263 // This is safe because we have `&mut self`, guaranteeing exclusive
264 // access. This avoids creating an intermediate `&mut` reference that
265 // could be invalidated, which was the source of the Miri error.
266 let inner_ptr = self.inner.get() as *mut HashMap<K2, V2, S>;
267
268 ReusableHashMapGuard {
269 inner: inner_ptr,
270 _parent: PhantomData,
271 }
272 }
273}
274
275impl<'parent, K1, V1, K2, V2, S> Drop for ReusableHashMapGuard<'parent, K1, V1, K2, V2, S>
276where
277 K1: 'static,
278 V1: 'static,
279 S: 'static + BuildHasher + Default,
280{
281 /// Clears the underlying `HashMap` upon being dropped.
282 ///
283 /// This is the core of the reuse mechanism. By clearing the map instead of
284 /// dropping it, we preserve its memory allocation (capacity) for the next
285 /// user. This avoids the cost of deallocation and reallocation.
286 ///
287 /// # Safety
288 ///
289 /// The pointer `self.inner` is guaranteed to be valid because the lifetime
290 /// `'parent` ensures the guard does not outlive the `ReusableHashMap` it
291 /// was created from.
292 fn drop(&mut self) {
293 // SAFETY: The pointer `self.inner` is guaranteed to be valid.
294 // We get a mutable reference and clear the map, making it ready for
295 // the next reuse.
296 unsafe {
297 (*self.inner).clear();
298 }
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use std::hash::BuildHasherDefault;
306 use std::sync::Mutex;
307 use twox_hash::XxHash64;
308
309 #[test]
310 fn reference_reuse_works() {
311 let mut map = ReusableHashMap::<&'static str, &'static str>::default();
312 {
313 let hello = String::from("Hello");
314 let world = String::from("World");
315 let mut r_map = map.recycle();
316 r_map.insert(hello.as_str(), world.as_str());
317 assert_eq!(r_map.get("Hello"), Some(&world.as_str()));
318 }
319 assert!(unsafe { (*map.inner.get()).is_empty() });
320 }
321
322 #[test]
323 fn string_identity_reuse_works() {
324 let mut map = ReusableHashMap::<String, String>::default();
325 {
326 let mut r_map = map.recycle::<String, String>();
327 r_map.insert("hello".to_string(), "world".to_string());
328 assert_eq!(r_map.get("hello"), Some(&"world".to_string()));
329 }
330 assert!(unsafe { (*map.inner.get()).is_empty() });
331 }
332
333 #[test]
334 fn primitive_reuse_works() {
335 let mut map = ReusableHashMap::<i32, i32>::default();
336 {
337 let mut r_map = map.recycle::<i32, i32>();
338 r_map.insert(1, 2);
339 assert_eq!(r_map.get(&1), Some(&2));
340 }
341 assert!(unsafe { (*map.inner.get()).is_empty() });
342 }
343
344 #[test]
345 fn empty_reuse_is_still_empty_after_drop() {
346 let mut map = ReusableHashMap::<String, String>::default();
347 {
348 let _r_map = map.recycle::<String, String>();
349 }
350 assert!(unsafe { (*map.inner.get()).is_empty() });
351 }
352
353 #[test]
354 fn sequential_reuse_works() {
355 let mut map = ReusableHashMap::<String, String>::default();
356 {
357 let mut r_map = map.recycle::<String, String>();
358 r_map.insert("one".to_string(), "1".to_string());
359 assert_eq!(r_map.len(), 1);
360 }
361 assert!(unsafe { (*map.inner.get()).is_empty() });
362 {
363 let mut r_map = map.recycle::<String, String>();
364 r_map.insert("two".to_string(), "2".to_string());
365 r_map.insert("three".to_string(), "3".to_string());
366 assert_eq!(r_map.len(), 2);
367 }
368 assert!(unsafe { (*map.inner.get()).is_empty() });
369 }
370
371 #[test]
372 fn custom_hasher_reuse_works() {
373 type CustomHasher = BuildHasherDefault<XxHash64>;
374 let mut map = ReusableHashMap::<i32, i32, CustomHasher>::default();
375 {
376 let mut r_map = map.recycle::<i32, i32>();
377 r_map.insert(1, 2);
378 assert_eq!(r_map.get(&1), Some(&2));
379 }
380 assert!(unsafe { (*map.inner.get()).is_empty() });
381 }
382
383 #[test]
384 fn mutex_reuse_works() {
385 let reusable_map = Mutex::new(ReusableHashMap::<i32, i32>::default());
386 {
387 let mut map_guard = reusable_map.lock().unwrap();
388 let mut r_map = map_guard.recycle::<i32, i32>();
389 r_map.insert(1, 2);
390 assert_eq!(r_map.get(&1), Some(&2));
391 }
392 let map_guard = reusable_map.lock().unwrap();
393 assert!(unsafe { (*map_guard.inner.get()).is_empty() });
394 }
395}