Skip to main content

oxc_allocator/
hash_map.rs

1//! A hash map without `Drop` that stores data in arena allocator.
2//!
3//! By default uses [`FxHasher`] to hash keys. The hasher can be customized via the `S` type parameter
4//! (e.g. `IdentBuildHasher` for `Ident` keys).
5//!
6//! See [`HashMap`] for more details.
7//!
8//! [`FxHasher`]: rustc_hash::FxHasher
9
10// All methods which just delegate to `hashbrown::HashMap` methods marked `#[inline(always)]`
11#![expect(clippy::inline_always)]
12
13use std::{
14    fmt,
15    hash::{BuildHasher, Hash},
16    mem::ManuallyDrop,
17    ops::{Deref, DerefMut},
18};
19
20use rustc_hash::FxBuildHasher;
21
22use crate::arena::Arena;
23
24// Re-export additional types from `hashbrown`
25pub use hashbrown::{
26    Equivalent, TryReserveError,
27    hash_map::{
28        Drain, Entry, EntryRef, ExtractIf, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys,
29        OccupiedError, Values, ValuesMut,
30    },
31};
32
33use crate::Allocator;
34
35type InnerHashMap<'alloc, K, V, S> = hashbrown::HashMap<K, V, S, &'alloc Arena>;
36
37/// A hash map without `Drop` that stores data in arena allocator.
38///
39/// Uses [`FxHasher`] by default. The hasher can be customized via the `S` type parameter.
40///
41/// Just a thin wrapper around [`hashbrown::HashMap`], which disables the `Drop` implementation.
42///
43/// All APIs are the same, except create a [`HashMap`] with
44/// either [`new_in`](HashMap::new_in) or [`with_capacity_in`](HashMap::with_capacity_in).
45///
46/// # No `Drop`s
47///
48/// Objects allocated into Oxc memory arenas are never [`Dropped`](Drop). Memory is released in bulk
49/// when the allocator is dropped, without dropping the individual objects in the arena.
50///
51/// Therefore, it would produce a memory leak if you allocated [`Drop`] types into the arena
52/// which own memory allocations outside the arena.
53///
54/// Static checks make this impossible to do. [`HashMap::new_in`] and all other methods which create
55/// a [`HashMap`] will refuse to compile if either key or value is a [`Drop`] type.
56///
57/// [`FxHasher`]: rustc_hash::FxHasher
58pub struct HashMap<'alloc, K, V, S = FxBuildHasher>(
59    pub(crate) ManuallyDrop<InnerHashMap<'alloc, K, V, S>>,
60);
61
62impl<K: fmt::Debug, V: fmt::Debug, S> fmt::Debug for HashMap<'_, K, V, S> {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_map().entries(self.0.iter()).finish()
65    }
66}
67
68/// SAFETY: Even though `Arena` is not `Sync`, we can make `HashMap<K, V>` `Sync` if both `K` and `V`
69/// are `Sync` because:
70///
71/// 1. No public methods allow access to the `&Arena` that `HashMap` contains (in `hashbrown::HashMap`),
72///    so user cannot illegally obtain 2 `&Arena`s on different threads via `HashMap`.
73///
74/// 2. All internal methods which access the `&Arena` take a `&mut self`.
75///    `&mut HashMap` cannot be transferred across threads, and nor can an owned `HashMap`
76///    (`HashMap` is not `Send`).
77///    Therefore these methods taking `&mut self` can be sure they're not operating on a `HashMap`
78///    which has been moved across threads.
79///
80/// Note: `HashMap` CANNOT be `Send`, even if `K` and `V` are `Send`, because that would allow 2 `HashMap`s
81/// on different threads to both allocate into same arena simultaneously. `Arena` is not thread-safe,
82/// and this would be undefined behavior.
83///
84/// ### Soundness holes
85///
86/// This is not actually fully sound. There are 2 holes I (@overlookmotel) am aware of:
87///
88/// 1. `allocator` method, which does allow access to the `&Arena` that `HashMap` contains.
89/// 2. `Clone` impl on `hashbrown::HashMap`, which may perform allocations in the arena, given only a
90///    `&self` reference.
91///
92/// [`HashMap::allocator`] prevents accidental access to the underlying method of `hashbrown::HashMap`,
93/// and `clone` called on a `&HashMap` clones the `&HashMap` reference, not the `HashMap` itself (harmless).
94/// But both can be accessed via explicit `Deref` (`hash_map.deref().allocator()` or `hash_map.deref().clone()`),
95/// so we don't have complete soundness.
96///
97/// To close these holes we need to remove `Deref` and `DerefMut` impls on `HashMap`, and instead add
98/// methods to `HashMap` itself which pass on calls to the inner `hashbrown::HashMap`.
99///
100/// TODO: Fix these holes.
101/// TODO: Remove any other methods that currently allow performing allocations with only a `&self` reference.
102unsafe impl<K: Sync, V: Sync, S: Sync> Sync for HashMap<'_, K, V, S> {}
103
104// TODO: `IntoIter`, `Drain`, and other consuming iterators provided by `hashbrown` are `Drop`.
105// Wrap them in `ManuallyDrop` to prevent that.
106
107impl<'alloc, K, V, S> HashMap<'alloc, K, V, S> {
108    /// Const assertions that `K` and `V` are not `Drop`.
109    /// Must be referenced in all methods which create a `HashMap`.
110    const ASSERT_K_AND_V_ARE_NOT_DROP: () = {
111        assert!(
112            !std::mem::needs_drop::<K>(),
113            "Cannot create a HashMap<K, V> where K is a Drop type"
114        );
115        assert!(
116            !std::mem::needs_drop::<V>(),
117            "Cannot create a HashMap<K, V> where V is a Drop type"
118        );
119    };
120
121    /// Creates an empty [`HashMap`] with the given hasher. It will be allocated with the given allocator.
122    ///
123    /// The hash map is initially created with a capacity of 0, so it will not allocate
124    /// until it is first inserted into.
125    #[inline(always)]
126    pub fn with_hasher_in(hasher: S, allocator: &'alloc Allocator) -> Self {
127        const { Self::ASSERT_K_AND_V_ARE_NOT_DROP };
128
129        let inner = InnerHashMap::with_hasher_in(hasher, allocator.arena());
130        Self(ManuallyDrop::new(inner))
131    }
132
133    /// Creates an empty [`HashMap`] with the specified capacity and hasher.
134    /// It will be allocated with the given allocator.
135    ///
136    /// The hash map will be able to hold at least capacity elements without reallocating.
137    /// If capacity is 0, the hash map will not allocate.
138    #[inline(always)]
139    pub fn with_capacity_and_hasher_in(
140        capacity: usize,
141        hasher: S,
142        allocator: &'alloc Allocator,
143    ) -> Self {
144        const { Self::ASSERT_K_AND_V_ARE_NOT_DROP };
145
146        let inner = InnerHashMap::with_capacity_and_hasher_in(capacity, hasher, allocator.arena());
147        Self(ManuallyDrop::new(inner))
148    }
149
150    /// Creates a consuming iterator visiting all the keys in arbitrary order.
151    ///
152    /// The map cannot be used after calling this. The iterator element type is `K`.
153    #[inline(always)]
154    pub fn into_keys(self) -> IntoKeys<K, V, &'alloc Arena> {
155        let inner = ManuallyDrop::into_inner(self.0);
156        inner.into_keys()
157    }
158
159    /// Creates a consuming iterator visiting all the values in arbitrary order.
160    ///
161    /// The map cannot be used after calling this. The iterator element type is `V`.
162    #[inline(always)]
163    pub fn into_values(self) -> IntoValues<K, V, &'alloc Arena> {
164        let inner = ManuallyDrop::into_inner(self.0);
165        inner.into_values()
166    }
167
168    /// Calling this method produces a compile-time panic.
169    ///
170    /// This method would be unsound, because [`HashMap`] is `Sync`, and the underlying allocator
171    /// (`Arena`) is not `Sync`.
172    ///
173    /// This method exists only to block access as much as possible to the underlying
174    /// `hashbrown::HashMap::allocator` method. That method can still be accessed via explicit `Deref`
175    /// (`hash_map.deref().allocator()`), but that's unsound.
176    ///
177    /// We'll prevent access to it completely and remove this method as soon as we can.
178    // TODO: Do that!
179    #[expect(clippy::unused_self)]
180    pub fn allocator(&self) -> &'alloc Arena {
181        const { panic!("This method cannot be called") };
182        unreachable!();
183    }
184}
185
186/// Methods for any hasher that implements [`Default`].
187///
188/// This includes [`FxBuildHasher`] and any custom hasher (e.g. `IdentBuildHasher`).
189impl<'alloc, K, V, S: Default> HashMap<'alloc, K, V, S> {
190    /// Creates an empty [`HashMap`]. It will be allocated with the given allocator.
191    ///
192    /// The hash map is initially created with a capacity of 0, so it will not allocate
193    /// until it is first inserted into.
194    #[inline(always)]
195    pub fn new_in(allocator: &'alloc Allocator) -> Self {
196        Self::with_hasher_in(S::default(), allocator)
197    }
198
199    /// Creates an empty [`HashMap`] with the specified capacity. It will be allocated with the given allocator.
200    ///
201    /// The hash map will be able to hold at least capacity elements without reallocating.
202    /// If capacity is 0, the hash map will not allocate.
203    #[inline(always)]
204    pub fn with_capacity_in(capacity: usize, allocator: &'alloc Allocator) -> Self {
205        Self::with_capacity_and_hasher_in(capacity, S::default(), allocator)
206    }
207
208    /// Create a new [`HashMap`] whose elements are taken from an iterator and
209    /// allocated in the given `allocator`.
210    ///
211    /// This is behaviorally identical to [`FromIterator::from_iter`].
212    #[inline]
213    pub fn from_iter_in<I: IntoIterator<Item = (K, V)>>(
214        iter: I,
215        allocator: &'alloc Allocator,
216    ) -> Self
217    where
218        K: Eq + Hash,
219        S: BuildHasher,
220    {
221        const { Self::ASSERT_K_AND_V_ARE_NOT_DROP };
222
223        let iter = iter.into_iter();
224
225        // Use the iterator's lower size bound.
226        // This follows `hashbrown::HashMap`'s `from_iter` implementation.
227        //
228        // This is a trade-off:
229        // * Negative: If lower bound is too low, the `HashMap` may have to grow and reallocate during `for_each` loop.
230        // * Positive: Avoids potential large over-allocation for iterators where upper bound may be a large over-estimate
231        //   e.g. filter iterators.
232        let capacity = iter.size_hint().0;
233        let map =
234            InnerHashMap::with_capacity_and_hasher_in(capacity, S::default(), allocator.arena());
235        // Wrap in `ManuallyDrop` *before* calling `for_each`, so compiler doesn't insert unnecessary code
236        // to drop the `FxHashMap` in case of a panic in iterator's `next` method
237        let mut map = ManuallyDrop::new(map);
238
239        iter.for_each(|(k, v)| {
240            map.insert(k, v);
241        });
242
243        Self(map)
244    }
245}
246
247// Provide access to all `hashbrown::HashMap`'s methods via deref
248impl<'alloc, K, V, S> Deref for HashMap<'alloc, K, V, S> {
249    type Target = InnerHashMap<'alloc, K, V, S>;
250
251    #[inline]
252    fn deref(&self) -> &Self::Target {
253        &self.0
254    }
255}
256
257impl<'alloc, K, V, S> DerefMut for HashMap<'alloc, K, V, S> {
258    #[inline]
259    fn deref_mut(&mut self) -> &mut InnerHashMap<'alloc, K, V, S> {
260        &mut self.0
261    }
262}
263
264impl<'alloc, K, V, S> IntoIterator for HashMap<'alloc, K, V, S> {
265    type IntoIter = IntoIter<K, V, &'alloc Arena>;
266    type Item = (K, V);
267
268    /// Creates a consuming iterator, that is, one that moves each key-value pair out of the map
269    /// in arbitrary order.
270    ///
271    /// The map cannot be used after calling this.
272    #[inline(always)]
273    fn into_iter(self) -> Self::IntoIter {
274        let inner = ManuallyDrop::into_inner(self.0);
275        // TODO: `hashbrown::hash_map::IntoIter` is `Drop`.
276        // Wrap it in `ManuallyDrop` to prevent that.
277        inner.into_iter()
278    }
279}
280
281impl<'alloc, 'i, K, V, S> IntoIterator for &'i HashMap<'alloc, K, V, S> {
282    type IntoIter = <&'i InnerHashMap<'alloc, K, V, S> as IntoIterator>::IntoIter;
283    type Item = (&'i K, &'i V);
284
285    /// Creates an iterator over the entries of a `HashMap` in arbitrary order.
286    ///
287    /// The iterator element type is `(&'a K, &'a V)`.
288    ///
289    /// Return the same [`Iter`] struct as by the `iter` method on [`HashMap`].
290    #[inline(always)]
291    fn into_iter(self) -> Self::IntoIter {
292        self.0.iter()
293    }
294}
295
296impl<'alloc, 'i, K, V, S> IntoIterator for &'i mut HashMap<'alloc, K, V, S> {
297    type IntoIter = <&'i mut InnerHashMap<'alloc, K, V, S> as IntoIterator>::IntoIter;
298    type Item = (&'i K, &'i mut V);
299
300    /// Creates an iterator over the entries of a `HashMap` in arbitrary order
301    /// with mutable references to the values.
302    ///
303    /// The iterator element type is `(&'a K, &'a mut V)`.
304    ///
305    /// Return the same [`IterMut`] struct as by the `iter_mut` method on [`HashMap`].
306    #[inline(always)]
307    fn into_iter(self) -> Self::IntoIter {
308        self.0.iter_mut()
309    }
310}
311
312impl<K, V, S> PartialEq for HashMap<'_, K, V, S>
313where
314    K: Eq + Hash,
315    V: PartialEq,
316    S: BuildHasher,
317{
318    #[inline(always)]
319    fn eq(&self, other: &Self) -> bool {
320        self.0.eq(&other.0)
321    }
322}
323
324impl<K, V, S> Eq for HashMap<'_, K, V, S>
325where
326    K: Eq + Hash,
327    V: Eq,
328    S: BuildHasher,
329{
330}
331
332// Note: `Index` and `Extend` are implemented via `Deref`