ic_stable_memory/collections/hash_set/
mod.rs1use crate::collections::hash_map::SHashMap;
2use crate::collections::hash_set::iter::SHashSetIter;
3use crate::encoding::AsFixedSizeBytes;
4use crate::primitive::StableType;
5use crate::OutOfMemory;
6use std::borrow::Borrow;
7use std::fmt::{Debug, Formatter};
8use std::hash::Hash;
9
10#[doc(hidden)]
11pub mod iter;
12
13pub struct SHashSet<T: StableType + AsFixedSizeBytes + Hash + Eq> {
17 map: SHashMap<T, ()>,
18}
19
20impl<T: StableType + AsFixedSizeBytes + Hash + Eq> SHashSet<T> {
21 #[inline]
23 pub fn new() -> Self {
24 Self {
25 map: SHashMap::new(),
26 }
27 }
28
29 #[inline]
31 pub fn new_with_capacity(capacity: usize) -> Result<Self, OutOfMemory> {
32 Ok(Self {
33 map: SHashMap::new_with_capacity(capacity)?,
34 })
35 }
36
37 #[inline]
39 pub fn insert(&mut self, value: T) -> Result<bool, T> {
40 self.map
41 .insert(value, ())
42 .map(|it| it.is_some())
43 .map_err(|(k, _)| k)
44 }
45
46 #[inline]
48 pub fn remove<Q>(&mut self, value: &Q) -> bool
49 where
50 T: Borrow<Q>,
51 Q: Hash + Eq + ?Sized,
52 {
53 self.map.remove(value).is_some()
54 }
55
56 #[inline]
58 pub fn contains<Q>(&self, value: &Q) -> bool
59 where
60 T: Borrow<Q>,
61 Q: Hash + Eq + ?Sized,
62 {
63 self.map.contains_key(value)
64 }
65
66 #[inline]
68 pub fn len(&self) -> usize {
69 self.map.len()
70 }
71
72 #[inline]
74 pub fn capacity(&self) -> usize {
75 self.map.capacity()
76 }
77
78 #[inline]
80 pub fn is_empty(&self) -> bool {
81 self.map.is_empty()
82 }
83
84 #[inline]
86 pub fn is_full(&self) -> bool {
87 self.map.is_full()
88 }
89
90 #[inline]
92 pub fn iter(&self) -> SHashSetIter<T> {
93 SHashSetIter::new(self)
94 }
95
96 #[inline]
98 pub fn clear(&mut self) {
99 self.map.clear();
100 }
101}
102
103impl<T: StableType + AsFixedSizeBytes + Hash + Eq> Default for SHashSet<T> {
104 #[inline]
105 fn default() -> Self {
106 SHashSet::new()
107 }
108}
109
110impl<T: StableType + AsFixedSizeBytes + Hash + Eq> AsFixedSizeBytes for SHashSet<T> {
111 const SIZE: usize = SHashMap::<T, ()>::SIZE;
112 type Buf = <SHashMap<T, ()> as AsFixedSizeBytes>::Buf;
113
114 #[inline]
115 fn as_fixed_size_bytes(&self, buf: &mut [u8]) {
116 self.map.as_fixed_size_bytes(buf)
117 }
118
119 #[inline]
120 fn from_fixed_size_bytes(arr: &[u8]) -> Self {
121 let map = SHashMap::<T, ()>::from_fixed_size_bytes(arr);
122 Self { map }
123 }
124}
125
126impl<T: StableType + AsFixedSizeBytes + Hash + Eq> StableType for SHashSet<T> {
127 #[inline]
128 unsafe fn stable_drop_flag_off(&mut self) {
129 self.map.stable_drop_flag_off();
130 }
131
132 #[inline]
133 unsafe fn stable_drop_flag_on(&mut self) {
134 self.map.stable_drop_flag_on();
135 }
136}
137
138impl<T: StableType + AsFixedSizeBytes + Hash + Eq + Debug> Debug for SHashSet<T> {
139 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
140 f.write_str("(")?;
141 for (idx, elem) in self.iter().enumerate() {
142 elem.fmt(f)?;
143
144 if idx < self.len() - 1 {
145 f.write_str(", ")?;
146 }
147 }
148 f.write_str(")")
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use crate::collections::hash_set::SHashSet;
155 use crate::encoding::{AsFixedSizeBytes, Buffer};
156 use crate::utils::test::generate_random_string;
157 use crate::utils::DebuglessUnwrap;
158 use crate::{
159 _debug_validate_allocator, get_allocated_size, init_allocator, retrieve_custom_data,
160 stable, stable_memory_init, stable_memory_post_upgrade, stable_memory_pre_upgrade,
161 store_custom_data, SBox,
162 };
163 use rand::rngs::ThreadRng;
164 use rand::{thread_rng, Rng};
165 use std::collections::HashSet;
166
167 #[test]
168 fn basic_flow_works_fine() {
169 stable::clear();
170 stable_memory_init();
171
172 {
173 let mut set = SHashSet::default();
174
175 assert!(set.is_empty());
176
177 assert!(!set.insert(10).unwrap());
178 assert!(!set.insert(20).unwrap());
179 assert!(set.insert(10).unwrap());
180
181 assert!(set.contains(&10));
182 assert!(!set.contains(&100));
183
184 assert_eq!(set.len(), 2);
185
186 assert!(!set.remove(&100));
187 assert!(set.remove(&10));
188
189 SHashSet::<u64>::new_with_capacity(10).debugless_unwrap();
190 }
191
192 _debug_validate_allocator();
193 assert_eq!(get_allocated_size(), 0);
194 }
195
196 #[test]
197 fn iter_works_fine() {
198 stable::clear();
199 stable_memory_init();
200
201 {
202 let mut set = SHashSet::default();
203
204 for i in 0..100 {
205 set.insert(i);
206 }
207
208 let mut c = 0;
209 for _ in set.iter() {
210 c += 1;
211 }
212
213 assert_eq!(c, 100);
214 }
215
216 _debug_validate_allocator();
217 assert_eq!(get_allocated_size(), 0);
218 }
219
220 #[test]
221 fn serialization_works_fine() {
222 stable::clear();
223 stable_memory_init();
224
225 {
226 let set = SHashSet::<u32>::default();
227
228 let len = set.len();
229 let cap = set.capacity();
230
231 let buf = set.as_new_fixed_size_bytes();
232 let set1 = SHashSet::<u32>::from_fixed_size_bytes(buf._deref());
233
234 assert_eq!(len, set1.len());
235 assert_eq!(cap, set1.capacity());
236 }
237
238 _debug_validate_allocator();
239 assert_eq!(get_allocated_size(), 0);
240 }
241
242 #[derive(Debug)]
243 enum Action {
244 Insert,
245 Remove,
246 Clear,
247 CanisterUpgrade,
248 }
249
250 struct Fuzzer {
251 set: Option<SHashSet<SBox<String>>>,
252 example: HashSet<String>,
253 keys: Vec<String>,
254 rng: ThreadRng,
255 log: Vec<Action>,
256 }
257
258 impl Fuzzer {
259 fn new() -> Fuzzer {
260 Fuzzer {
261 set: Some(SHashSet::new()),
262 example: HashSet::new(),
263 keys: Vec::new(),
264 rng: thread_rng(),
265 log: Vec::new(),
266 }
267 }
268
269 fn set(&mut self) -> &mut SHashSet<SBox<String>> {
270 self.set.as_mut().unwrap()
271 }
272
273 fn next(&mut self) {
274 let action = self.rng.gen_range(0..101);
275
276 match action {
277 0..=59 => {
279 let key = generate_random_string(&mut self.rng);
280 if let Ok(data) = SBox::new(key.clone()) {
281 if self.set().insert(data).is_err() {
282 return;
283 }
284 self.example.insert(key.clone());
285
286 match self.keys.binary_search(&key) {
287 Ok(idx) => {}
288 Err(idx) => {
289 self.keys.insert(idx, key);
290 }
291 };
292
293 self.log.push(Action::Insert);
294 }
295 }
296 60..=89 => {
298 let len = self.set().len();
299
300 if len == 0 {
301 return self.next();
302 }
303
304 let idx = self.rng.gen_range(0..len);
305 let key = self.keys.remove(idx);
306
307 self.set().remove(&key);
308 self.example.remove(&key);
309
310 self.log.push(Action::Remove);
311 }
312 90..=91 => {
313 self.set().clear();
314 self.example.clear();
315 self.keys.clear();
316
317 self.log.push(Action::Clear);
318 }
319 _ => match SBox::new(self.set.take().unwrap()) {
321 Ok(data) => {
322 store_custom_data(1, data);
323
324 if stable_memory_pre_upgrade().is_ok() {
325 stable_memory_post_upgrade();
326 }
327
328 self.set = retrieve_custom_data::<SHashSet<SBox<String>>>(1)
329 .map(|it| it.into_inner());
330
331 self.log.push(Action::CanisterUpgrade);
332 }
333 Err(set) => {
334 self.set = Some(set);
335 }
336 },
337 }
338
339 _debug_validate_allocator();
340 assert_eq!(self.set().len() as usize, self.example.len());
341
342 for key in self.keys.clone() {
343 assert!(self.set().contains(&key));
344 assert!(self.example.contains(&key));
345 }
346 }
347 }
348
349 #[test]
350 fn fuzzer_works_fine() {
351 stable::clear();
352 init_allocator(0);
353
354 {
355 let mut fuzzer = Fuzzer::new();
356
357 for _ in 0..10_000 {
358 fuzzer.next();
359 }
360 }
361
362 assert_eq!(get_allocated_size(), 0);
363 }
364
365 #[test]
366 fn fuzzer_works_fine_limited_memory() {
367 stable::clear();
368 init_allocator(10);
369
370 {
371 let mut fuzzer = Fuzzer::new();
372
373 for _ in 0..10_000 {
374 fuzzer.next();
375 }
376 }
377
378 assert_eq!(get_allocated_size(), 0);
379 }
380}