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`