Skip to content

Commit b32ba2a

Browse files
committed
feat(allocator): add HashMap::from_iter_in
1 parent 6065c44 commit b32ba2a

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//! A hash set without `Drop`, that uses [`FxHasher`] to hash keys, and stores data in arena allocator.
2+
//!
3+
//! See [`HashSet`] for more details.
4+
//!
5+
//! [`FxHasher`]: rustc_hash::FxHasher
6+
7+
// All methods which just delegate to `HashMap` methods marked `#[inline(always)]`
8+
#![expect(clippy::inline_always)]
9+
10+
use std::{hash::Hash, mem::ManuallyDrop};
11+
12+
use bumpalo::Bump;
13+
14+
use crate::{
15+
Allocator,
16+
hash_map::{HashMap, IntoKeys, Keys},
17+
};
18+
19+
/// A hash set without `Drop`, that uses [`FxHasher`] to hash keys, and stores data in arena allocator.
20+
///
21+
/// Just a thin wrapper around [`HashMap<T, ()>`], which provides set semantics.
22+
///
23+
/// All APIs are similar to `std::collections::HashSet`, except create a [`HashSet`] with
24+
/// either [`new_in`](HashSet::new_in) or [`with_capacity_in`](HashSet::with_capacity_in).
25+
///
26+
/// # No `Drop`s
27+
///
28+
/// Objects allocated into Oxc memory arenas are never [`Dropped`](Drop). Memory is released in bulk
29+
/// when the allocator is dropped, without dropping the individual objects in the arena.
30+
///
31+
/// Therefore, it would produce a memory leak if you allocated [`Drop`] types into the arena
32+
/// which own memory allocations outside the arena.
33+
///
34+
/// Static checks make this impossible to do. [`HashSet::new_in`] and all other methods which create
35+
/// a [`HashSet`] will refuse to compile if the key is a [`Drop`] type.
36+
///
37+
/// [`FxHasher`]: rustc_hash::FxHasher
38+
pub struct HashSet<'alloc, T>(ManuallyDrop<HashMap<'alloc, T, ()>>);
39+
40+
/// SAFETY: `HashSet` is `Sync` if `T` is `Sync` because it's just a wrapper around `HashMap<T, ()>`,
41+
/// which is already `Sync` when `T` is `Sync`.
42+
unsafe impl<T: Sync> Sync for HashSet<'_, T> {}
43+
44+
impl<'alloc, T> HashSet<'alloc, T> {
45+
/// Creates an empty [`HashSet`]. It will be allocated with the given allocator.
46+
///
47+
/// The hash set is initially created with a capacity of 0, so it will not allocate
48+
/// until it is first inserted into.
49+
#[inline(always)]
50+
pub fn new_in(allocator: &'alloc Allocator) -> Self {
51+
Self(ManuallyDrop::new(HashMap::new_in(allocator)))
52+
}
53+
54+
/// Creates an empty [`HashSet`] with the specified capacity. It will be allocated with the given allocator.
55+
///
56+
/// The hash set will be able to hold at least capacity elements without reallocating.
57+
/// If capacity is 0, the hash set will not allocate.
58+
#[inline(always)]
59+
pub fn with_capacity_in(capacity: usize, allocator: &'alloc Allocator) -> Self {
60+
Self(ManuallyDrop::new(HashMap::with_capacity_in(capacity, allocator)))
61+
}
62+
63+
/// Create a new [`HashSet`] whose elements are taken from an iterator and
64+
/// allocated in the given `allocator`.
65+
///
66+
/// This is behaviorially identical to [`FromIterator::from_iter`].
67+
#[inline]
68+
pub fn from_iter_in<I: IntoIterator<Item = T>>(iter: I, allocator: &'alloc Allocator) -> Self
69+
where
70+
T: Eq + Hash,
71+
{
72+
Self(ManuallyDrop::new(HashMap::from_iter_in(iter.into_iter().map(|k| (k, ())), allocator)))
73+
}
74+
75+
/// Returns the number of elements the set can hold without reallocating.
76+
#[inline(always)]
77+
pub fn capacity(&self) -> usize {
78+
self.0.capacity()
79+
}
80+
81+
/// An iterator visiting all elements in arbitrary order.
82+
/// The iterator element type is `&'a T`.
83+
#[inline(always)]
84+
pub fn iter(&self) -> impl Iterator<Item = &T> {
85+
self.0.keys()
86+
}
87+
88+
/// Returns the number of elements in the set.
89+
#[inline(always)]
90+
pub fn len(&self) -> usize {
91+
self.0.len()
92+
}
93+
94+
/// Returns `true` if the set contains no elements.
95+
#[inline(always)]
96+
pub fn is_empty(&self) -> bool {
97+
self.0.is_empty()
98+
}
99+
100+
/// Clears the set, removing all values.
101+
#[inline(always)]
102+
pub fn clear(&mut self) {
103+
self.0.clear();
104+
}
105+
106+
/// Returns `true` if the set contains a value.
107+
#[inline(always)]
108+
pub fn contains<Q>(&self, value: &Q) -> bool
109+
where
110+
T: std::borrow::Borrow<Q> + Eq + Hash,
111+
Q: Hash + Eq + ?Sized,
112+
{
113+
self.0.contains_key(value)
114+
}
115+
116+
/// Adds a value to the set.
117+
///
118+
/// Returns whether the value was newly inserted. That is:
119+
///
120+
/// - If the set did not previously contain this value, `true` is returned.
121+
/// - If the set already contained this value, `false` is returned.
122+
#[inline(always)]
123+
pub fn insert(&mut self, value: T) -> bool
124+
where
125+
T: Eq + Hash,
126+
{
127+
self.0.insert(value, ()).is_none()
128+
}
129+
130+
/// Removes a value from the set. Returns whether the value was present in the set.
131+
#[inline(always)]
132+
pub fn remove<Q>(&mut self, value: &Q) -> bool
133+
where
134+
T: std::borrow::Borrow<Q> + Eq + Hash,
135+
Q: Hash + Eq + ?Sized,
136+
{
137+
self.0.remove(value).is_some()
138+
}
139+
140+
/// Reserves capacity for at least `additional` more elements to be inserted
141+
/// in the `HashSet`. The collection may reserve more space to speculatively
142+
/// avoid frequent reallocations.
143+
#[inline(always)]
144+
pub fn reserve(&mut self, additional: usize)
145+
where
146+
T: Eq + Hash,
147+
{
148+
self.0.reserve(additional);
149+
}
150+
151+
/// Visits the values representing the difference, i.e., the values that are in `self` but not in `other`.
152+
#[inline]
153+
pub fn difference<'a>(&'a self, other: &'a HashSet<'alloc, T>) -> impl Iterator<Item = &'a T>
154+
where
155+
T: Eq + Hash,
156+
{
157+
self.iter().filter(|&v| !other.contains(v))
158+
}
159+
}
160+
161+
impl<'alloc, T> IntoIterator for HashSet<'alloc, T> {
162+
type IntoIter = IntoKeys<T, (), &'alloc Bump>;
163+
type Item = T;
164+
165+
/// Creates a consuming iterator, that is, one that moves each value out of the set
166+
/// in arbitrary order.
167+
///
168+
/// The set cannot be used after calling this.
169+
#[inline(always)]
170+
fn into_iter(self) -> Self::IntoIter {
171+
let inner = ManuallyDrop::into_inner(self.0);
172+
inner.into_keys()
173+
}
174+
}
175+
176+
impl<'i, T> IntoIterator for &'i HashSet<'_, T> {
177+
type IntoIter = Keys<'i, T, ()>;
178+
type Item = &'i T;
179+
180+
/// Creates an iterator over the values of a `HashSet` in arbitrary order.
181+
///
182+
/// The iterator element type is `&'a T`.
183+
#[inline(always)]
184+
fn into_iter(self) -> Self::IntoIter {
185+
self.0.keys()
186+
}
187+
}
188+
189+
impl<T> PartialEq for HashSet<'_, T>
190+
where
191+
T: Eq + Hash,
192+
{
193+
#[inline(always)]
194+
fn eq(&self, other: &Self) -> bool {
195+
self.0.eq(&other.0)
196+
}
197+
}
198+
199+
impl<T> Eq for HashSet<'_, T> where T: Eq + Hash {}

crates/oxc_allocator/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//! * [`Vec`]
1010
//! * [`String`]
1111
//! * [`HashMap`]
12+
//! * [`HashSet`]
1213
//!
1314
//! See [`Allocator`] docs for information on efficient use of [`Allocator`].
1415
//!
@@ -50,6 +51,7 @@ mod convert;
5051
#[cfg(feature = "from_raw_parts")]
5152
mod from_raw_parts;
5253
pub mod hash_map;
54+
pub mod hash_set;
5355
#[cfg(feature = "pool")]
5456
mod pool;
5557
mod string_builder;
@@ -68,6 +70,7 @@ pub use boxed::Box;
6870
pub use clone_in::CloneIn;
6971
pub use convert::{FromIn, IntoIn};
7072
pub use hash_map::HashMap;
73+
pub use hash_set::HashSet;
7174
#[cfg(feature = "pool")]
7275
pub use pool::*;
7376
pub use string_builder::StringBuilder;

0 commit comments

Comments
 (0)