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}