Skip to main content

nautilus_core/
collections.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Abstraction layer over common hash-based containers.
17
18use std::{
19    collections::{HashMap, HashSet},
20    fmt::{Debug, Display},
21    hash::Hash,
22    sync::Arc,
23};
24
25use ahash::{AHashMap, AHashSet};
26use arc_swap::ArcSwap;
27use ustr::Ustr;
28
29/// A lock-free concurrent map optimized for read-heavy access patterns.
30///
31/// Reads are a single atomic pointer load with no contention between readers.
32/// Writes clone the inner map, mutate the clone, and atomically swap it in.
33///
34/// Not safe for concurrent writers using `load`/`store`: the last `store` wins
35/// and earlier updates are silently lost. Use [`rcu`](Self::rcu) when multiple
36/// writers may race, or restrict writes to a single task.
37///
38/// Wrap in `Arc` for shared ownership across threads.
39pub struct AtomicMap<K, V>(ArcSwap<AHashMap<K, V>>);
40
41impl<K, V> AtomicMap<K, V> {
42    /// Creates a new empty atomic map.
43    #[must_use]
44    pub fn new() -> Self {
45        Self(ArcSwap::new(Arc::new(AHashMap::new())))
46    }
47
48    /// Returns a snapshot guard for direct access to the inner map.
49    ///
50    /// The guard dereferences to `AHashMap<K, V>`. Use for operations that
51    /// need a reference into the map (e.g., `load().get(&key)`).
52    #[inline]
53    pub fn load(&self) -> arc_swap::Guard<Arc<AHashMap<K, V>>> {
54        self.0.load()
55    }
56
57    /// Atomically replaces the inner map.
58    pub fn store(&self, map: AHashMap<K, V>) {
59        self.0.store(Arc::new(map));
60    }
61}
62
63impl<K, V> AtomicMap<K, V>
64where
65    K: Eq + Hash + Clone,
66    V: Clone,
67{
68    /// Atomically applies `f` to a clone of the inner map.
69    ///
70    /// Retries if another writer swapped the map between the clone and the
71    /// compare-and-swap, so `f` may run more than once.
72    pub fn rcu<F>(&self, mut f: F)
73    where
74        F: FnMut(&mut AHashMap<K, V>),
75    {
76        self.0.rcu(|m| {
77            let mut m = (**m).clone();
78            f(&mut m);
79            m
80        });
81    }
82
83    /// Returns `true` if the map contains the given key.
84    #[inline]
85    pub fn contains_key(&self, key: &K) -> bool {
86        self.0.load().contains_key(key)
87    }
88
89    /// Returns a clone of the value for the given key, if present.
90    #[inline]
91    pub fn get_cloned(&self, key: &K) -> Option<V> {
92        self.0.load().get(key).cloned()
93    }
94
95    /// Inserts a key-value pair (clone-and-swap).
96    #[allow(
97        clippy::needless_pass_by_value,
98        reason = "by-value matches HashMap::insert; clone needed because rcu may retry"
99    )]
100    pub fn insert(&self, key: K, value: V) {
101        self.rcu(|m| {
102            m.insert(key.clone(), value.clone());
103        });
104    }
105
106    /// Removes a key (clone-and-swap).
107    pub fn remove(&self, key: &K) {
108        self.rcu(|m| {
109            m.remove(key);
110        });
111    }
112
113    /// Returns the number of entries.
114    #[inline]
115    pub fn len(&self) -> usize {
116        self.0.load().len()
117    }
118
119    /// Returns `true` if the map is empty.
120    #[inline]
121    pub fn is_empty(&self) -> bool {
122        self.0.load().is_empty()
123    }
124}
125
126impl<K, V> Default for AtomicMap<K, V> {
127    fn default() -> Self {
128        Self::new()
129    }
130}
131
132impl<K: Debug + Eq + Hash, V: Debug> Debug for AtomicMap<K, V> {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        f.debug_map().entries(self.0.load().iter()).finish()
135    }
136}
137
138impl<K: Eq + Hash, V> From<AHashMap<K, V>> for AtomicMap<K, V> {
139    fn from(map: AHashMap<K, V>) -> Self {
140        Self(ArcSwap::new(Arc::new(map)))
141    }
142}
143
144/// A lock-free concurrent set optimized for read-heavy access patterns.
145///
146/// Reads are a single atomic pointer load with no contention between readers.
147/// Writes clone the inner set, mutate the clone, and atomically swap it in.
148///
149/// Not safe for concurrent writers using `load`/`store`: the last `store` wins
150/// and earlier updates are silently lost. Use [`rcu`](Self::rcu) when multiple
151/// writers may race, or restrict writes to a single task.
152///
153/// Wrap in `Arc` for shared ownership across threads.
154pub struct AtomicSet<K>(ArcSwap<AHashSet<K>>);
155
156impl<K> AtomicSet<K> {
157    /// Creates a new empty atomic set.
158    #[must_use]
159    pub fn new() -> Self {
160        Self(ArcSwap::new(Arc::new(AHashSet::new())))
161    }
162
163    /// Returns a snapshot guard for direct access to the inner set.
164    ///
165    /// The guard dereferences to `AHashSet<K>`. Use for operations that
166    /// need iteration or reference access.
167    #[inline]
168    pub fn load(&self) -> arc_swap::Guard<Arc<AHashSet<K>>> {
169        self.0.load()
170    }
171
172    /// Atomically replaces the inner set.
173    pub fn store(&self, set: AHashSet<K>) {
174        self.0.store(Arc::new(set));
175    }
176}
177
178impl<K> AtomicSet<K>
179where
180    K: Eq + Hash + Clone,
181{
182    /// Atomically applies `f` to a clone of the inner set.
183    ///
184    /// Retries if another writer swapped the set between the clone and the
185    /// compare-and-swap, so `f` may run more than once.
186    pub fn rcu<F>(&self, mut f: F)
187    where
188        F: FnMut(&mut AHashSet<K>),
189    {
190        self.0.rcu(|s| {
191            let mut s = (**s).clone();
192            f(&mut s);
193            s
194        });
195    }
196
197    /// Returns `true` if the set contains the given key.
198    #[inline]
199    pub fn contains(&self, key: &K) -> bool {
200        self.0.load().contains(key)
201    }
202
203    /// Inserts a key (clone-and-swap).
204    #[allow(
205        clippy::needless_pass_by_value,
206        reason = "by-value matches HashSet::insert; clone needed because rcu may retry"
207    )]
208    pub fn insert(&self, key: K) {
209        self.rcu(|s| {
210            s.insert(key.clone());
211        });
212    }
213
214    /// Removes a key (clone-and-swap).
215    pub fn remove(&self, key: &K) {
216        self.rcu(|s| {
217            s.remove(key);
218        });
219    }
220
221    /// Returns the number of entries.
222    #[inline]
223    pub fn len(&self) -> usize {
224        self.0.load().len()
225    }
226
227    /// Returns `true` if the set is empty.
228    #[inline]
229    pub fn is_empty(&self) -> bool {
230        self.0.load().is_empty()
231    }
232}
233
234impl<K> Default for AtomicSet<K> {
235    fn default() -> Self {
236        Self::new()
237    }
238}
239
240impl<K: Debug + Eq + Hash> Debug for AtomicSet<K> {
241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        f.debug_set().entries(self.0.load().iter()).finish()
243    }
244}
245
246impl<K: Eq + Hash> From<AHashSet<K>> for AtomicSet<K> {
247    fn from(set: AHashSet<K>) -> Self {
248        Self(ArcSwap::new(Arc::new(set)))
249    }
250}
251
252/// Represents a generic set-like container with members.
253pub trait SetLike {
254    /// The type of items stored in the set.
255    type Item: Hash + Eq + Display + Clone;
256
257    /// Returns `true` if the set contains the specified item.
258    fn contains(&self, item: &Self::Item) -> bool;
259    /// Returns `true` if the set is empty.
260    fn is_empty(&self) -> bool;
261}
262
263impl<T, S> SetLike for HashSet<T, S>
264where
265    T: Eq + Hash + Display + Clone,
266    S: std::hash::BuildHasher,
267{
268    type Item = T;
269
270    #[inline]
271    fn contains(&self, v: &T) -> bool {
272        Self::contains(self, v)
273    }
274
275    #[inline]
276    fn is_empty(&self) -> bool {
277        Self::is_empty(self)
278    }
279}
280
281impl<T, S> SetLike for indexmap::IndexSet<T, S>
282where
283    T: Eq + Hash + Display + Clone,
284    S: std::hash::BuildHasher,
285{
286    type Item = T;
287
288    #[inline]
289    fn contains(&self, v: &T) -> bool {
290        Self::contains(self, v)
291    }
292
293    #[inline]
294    fn is_empty(&self) -> bool {
295        Self::is_empty(self)
296    }
297}
298
299impl<T, S> SetLike for ahash::AHashSet<T, S>
300where
301    T: Eq + Hash + Display + Clone,
302    S: std::hash::BuildHasher,
303{
304    type Item = T;
305
306    #[inline]
307    fn contains(&self, v: &T) -> bool {
308        self.get(v).is_some()
309    }
310
311    #[inline]
312    fn is_empty(&self) -> bool {
313        self.len() == 0
314    }
315}
316
317/// Represents a generic map-like container with key-value pairs.
318pub trait MapLike {
319    /// The type of keys stored in the map.
320    type Key: Hash + Eq + Display + Clone;
321    /// The type of values stored in the map.
322    type Value: Debug;
323
324    /// Returns `true` if the map contains the specified key.
325    fn contains_key(&self, key: &Self::Key) -> bool;
326    /// Returns `true` if the map is empty.
327    fn is_empty(&self) -> bool;
328}
329
330impl<K, V, S> MapLike for HashMap<K, V, S>
331where
332    K: Eq + Hash + Display + Clone,
333    V: Debug,
334    S: std::hash::BuildHasher,
335{
336    type Key = K;
337    type Value = V;
338
339    #[inline]
340    fn contains_key(&self, k: &K) -> bool {
341        self.contains_key(k)
342    }
343
344    #[inline]
345    fn is_empty(&self) -> bool {
346        self.is_empty()
347    }
348}
349
350impl<K, V, S> MapLike for indexmap::IndexMap<K, V, S>
351where
352    K: Eq + Hash + Display + Clone,
353    V: Debug,
354    S: std::hash::BuildHasher,
355{
356    type Key = K;
357    type Value = V;
358
359    #[inline]
360    fn contains_key(&self, k: &K) -> bool {
361        self.get(k).is_some()
362    }
363
364    #[inline]
365    fn is_empty(&self) -> bool {
366        self.is_empty()
367    }
368}
369
370impl<K, V, S> MapLike for ahash::AHashMap<K, V, S>
371where
372    K: Eq + Hash + Display + Clone,
373    V: Debug,
374    S: std::hash::BuildHasher,
375{
376    type Key = K;
377    type Value = V;
378
379    #[inline]
380    fn contains_key(&self, k: &K) -> bool {
381        self.get(k).is_some()
382    }
383
384    #[inline]
385    fn is_empty(&self) -> bool {
386        self.len() == 0
387    }
388}
389
390/// Convert any iterator of string-like items into a `Vec<Ustr>`.
391#[must_use]
392pub fn into_ustr_vec<I, T>(iter: I) -> Vec<Ustr>
393where
394    I: IntoIterator<Item = T>,
395    T: AsRef<str>,
396{
397    let iter = iter.into_iter();
398    let (lower, _) = iter.size_hint();
399    let mut result = Vec::with_capacity(lower);
400
401    for item in iter {
402        result.push(Ustr::from(item.as_ref()));
403    }
404
405    result
406}
407
408#[cfg(test)]
409#[allow(
410    clippy::unnecessary_to_owned,
411    reason = "Required for trait bound satisfaction"
412)]
413mod tests {
414    use std::{
415        collections::{HashMap, HashSet},
416        sync::{Arc, Barrier},
417    };
418
419    use ahash::{AHashMap, AHashSet};
420    use indexmap::{IndexMap, IndexSet};
421    use rstest::*;
422    use ustr::Ustr;
423
424    use super::*;
425
426    #[rstest]
427    fn test_atomic_set_new_is_empty() {
428        let set: AtomicSet<String> = AtomicSet::new();
429        assert!(set.is_empty());
430        assert_eq!(set.len(), 0);
431    }
432
433    #[rstest]
434    fn test_atomic_set_default_is_empty() {
435        let set: AtomicSet<u64> = AtomicSet::default();
436        assert!(set.is_empty());
437    }
438
439    #[rstest]
440    fn test_atomic_set_insert_and_contains() {
441        let set = AtomicSet::new();
442        set.insert(1);
443        set.insert(2);
444
445        assert!(set.contains(&1));
446        assert!(set.contains(&2));
447        assert!(!set.contains(&3));
448        assert_eq!(set.len(), 2);
449    }
450
451    #[rstest]
452    fn test_atomic_set_insert_duplicate() {
453        let set = AtomicSet::new();
454        set.insert(1);
455        set.insert(1);
456
457        assert_eq!(set.len(), 1);
458        assert!(set.contains(&1));
459    }
460
461    #[rstest]
462    fn test_atomic_set_remove() {
463        let set = AtomicSet::new();
464        set.insert(1);
465        set.insert(2);
466        set.remove(&1);
467
468        assert!(!set.contains(&1));
469        assert!(set.contains(&2));
470        assert_eq!(set.len(), 1);
471    }
472
473    #[rstest]
474    fn test_atomic_set_remove_nonexistent() {
475        let set: AtomicSet<i32> = AtomicSet::new();
476        set.insert(1);
477        set.remove(&999);
478
479        assert_eq!(set.len(), 1);
480        assert!(set.contains(&1));
481    }
482
483    #[rstest]
484    fn test_atomic_set_store_replaces_contents() {
485        let set = AtomicSet::new();
486        set.insert(1);
487        set.insert(2);
488
489        let mut replacement = AHashSet::new();
490        replacement.insert(10);
491        replacement.insert(20);
492        set.store(replacement);
493
494        assert!(!set.contains(&1));
495        assert!(!set.contains(&2));
496        assert!(set.contains(&10));
497        assert!(set.contains(&20));
498        assert_eq!(set.len(), 2);
499    }
500
501    #[rstest]
502    fn test_atomic_set_store_empty_clears() {
503        let set = AtomicSet::new();
504        set.insert(1);
505        set.store(AHashSet::new());
506
507        assert!(set.is_empty());
508    }
509
510    #[rstest]
511    fn test_atomic_set_rcu_batch_insert() {
512        let set = AtomicSet::new();
513        set.rcu(|s| {
514            s.insert(1);
515            s.insert(2);
516            s.insert(3);
517        });
518
519        assert_eq!(set.len(), 3);
520        assert!(set.contains(&1));
521        assert!(set.contains(&2));
522        assert!(set.contains(&3));
523    }
524
525    #[rstest]
526    fn test_atomic_set_rcu_mixed_operations() {
527        let set = AtomicSet::new();
528        set.insert(1);
529        set.insert(2);
530
531        set.rcu(|s| {
532            s.remove(&1);
533            s.insert(3);
534        });
535
536        assert!(!set.contains(&1));
537        assert!(set.contains(&2));
538        assert!(set.contains(&3));
539    }
540
541    #[rstest]
542    fn test_atomic_set_load_returns_snapshot() {
543        let set = AtomicSet::new();
544        set.insert(1);
545
546        let snapshot = set.load();
547        assert!(snapshot.contains(&1));
548        assert_eq!(snapshot.len(), 1);
549    }
550
551    #[rstest]
552    fn test_atomic_set_load_snapshot_not_affected_by_later_writes() {
553        let set = AtomicSet::new();
554        set.insert(1);
555
556        let snapshot = set.load();
557        set.insert(2);
558
559        assert!(!snapshot.contains(&2));
560        assert!(set.contains(&2));
561    }
562
563    #[rstest]
564    fn test_atomic_set_from_ahashset() {
565        let mut source = AHashSet::new();
566        source.insert("a".to_string());
567        source.insert("b".to_string());
568
569        let set = AtomicSet::from(source);
570
571        assert_eq!(set.len(), 2);
572        assert!(set.contains(&"a".to_string()));
573        assert!(set.contains(&"b".to_string()));
574    }
575
576    #[rstest]
577    fn test_atomic_set_debug() {
578        let set = AtomicSet::new();
579        set.insert(42);
580
581        let debug_str = format!("{set:?}");
582        assert!(debug_str.contains("42"));
583    }
584
585    #[rstest]
586    fn test_atomic_set_debug_empty() {
587        let set: AtomicSet<i32> = AtomicSet::new();
588        let debug_str = format!("{set:?}");
589        assert_eq!(debug_str, "{}");
590    }
591
592    #[rstest]
593    fn test_atomic_set_load_iteration() {
594        let set = AtomicSet::new();
595        set.insert(1);
596        set.insert(2);
597        set.insert(3);
598
599        let guard = set.load();
600        let mut values: Vec<_> = guard.iter().copied().collect();
601        values.sort_unstable();
602
603        assert_eq!(values, vec![1, 2, 3]);
604    }
605
606    #[rstest]
607    fn test_atomic_set_concurrent_reads() {
608        let set = Arc::new(AtomicSet::new());
609        for i in 0..100 {
610            set.insert(i);
611        }
612
613        let barrier = Arc::new(Barrier::new(8));
614        let handles: Vec<_> = (0..8)
615            .map(|_| {
616                let set = Arc::clone(&set);
617                let barrier = Arc::clone(&barrier);
618                std::thread::spawn(move || {
619                    barrier.wait();
620                    for i in 0..100 {
621                        assert!(set.contains(&i));
622                    }
623                })
624            })
625            .collect();
626
627        for h in handles {
628            h.join().unwrap();
629        }
630    }
631
632    #[rstest]
633    fn test_atomic_set_concurrent_rcu_writes() {
634        let set = Arc::new(AtomicSet::new());
635        let barrier = Arc::new(Barrier::new(4));
636
637        let handles: Vec<_> = (0..4u32)
638            .map(|t| {
639                let set = Arc::clone(&set);
640                let barrier = Arc::clone(&barrier);
641                std::thread::spawn(move || {
642                    barrier.wait();
643                    for i in 0..25 {
644                        set.insert(t * 25 + i);
645                    }
646                })
647            })
648            .collect();
649
650        for h in handles {
651            h.join().unwrap();
652        }
653
654        assert_eq!(set.len(), 100);
655        for i in 0..100 {
656            assert!(set.contains(&i), "missing {i}");
657        }
658    }
659
660    #[rstest]
661    fn test_atomic_set_concurrent_read_write() {
662        let set = Arc::new(AtomicSet::new());
663        for i in 0u32..100 {
664            set.insert(i);
665        }
666
667        let barrier = Arc::new(Barrier::new(5));
668
669        let writer = {
670            let set = Arc::clone(&set);
671            let barrier = Arc::clone(&barrier);
672            std::thread::spawn(move || {
673                barrier.wait();
674                for i in 100u32..200 {
675                    set.insert(i);
676                }
677            })
678        };
679
680        let readers: Vec<_> = (0..4)
681            .map(|_| {
682                let set = Arc::clone(&set);
683                let barrier = Arc::clone(&barrier);
684                std::thread::spawn(move || {
685                    barrier.wait();
686                    for _ in 0..1000 {
687                        let snapshot = set.load();
688                        let len = snapshot.len();
689                        assert!(
690                            (100..=200).contains(&len),
691                            "snapshot len {len} outside expected range"
692                        );
693                        for i in 0u32..100 {
694                            assert!(snapshot.contains(&i), "original key {i} missing");
695                        }
696                    }
697                })
698            })
699            .collect();
700
701        writer.join().unwrap();
702        for r in readers {
703            r.join().unwrap();
704        }
705
706        assert_eq!(set.len(), 200);
707    }
708
709    #[rstest]
710    fn test_atomic_set_snapshot_consistency_under_store() {
711        let set = Arc::new(AtomicSet::new());
712        let barrier = Arc::new(Barrier::new(5));
713
714        let writer = {
715            let set = Arc::clone(&set);
716            let barrier = Arc::clone(&barrier);
717            std::thread::spawn(move || {
718                barrier.wait();
719                for batch in 0u32..50 {
720                    let start = batch * 10;
721                    let new_set: AHashSet<u32> = (start..start + 10).collect();
722                    set.store(new_set);
723                }
724            })
725        };
726
727        let readers: Vec<_> = (0..4)
728            .map(|_| {
729                let set = Arc::clone(&set);
730                let barrier = Arc::clone(&barrier);
731                std::thread::spawn(move || {
732                    barrier.wait();
733                    for _ in 0..5000 {
734                        let snapshot = set.load();
735                        let items: Vec<u32> = snapshot.iter().copied().collect();
736                        if items.is_empty() {
737                            continue;
738                        }
739                        assert_eq!(
740                            items.len(),
741                            10,
742                            "partial snapshot: got {} items: {items:?}",
743                            items.len()
744                        );
745                        let min = *items.iter().min().unwrap();
746                        let max = *items.iter().max().unwrap();
747                        assert_eq!(
748                            max - min,
749                            9,
750                            "snapshot not from single batch: min={min} max={max}"
751                        );
752                    }
753                })
754            })
755            .collect();
756
757        writer.join().unwrap();
758        for r in readers {
759            r.join().unwrap();
760        }
761    }
762
763    #[rstest]
764    fn test_atomic_map_new_is_empty() {
765        let map: AtomicMap<String, i32> = AtomicMap::new();
766        assert!(map.is_empty());
767        assert_eq!(map.len(), 0);
768    }
769
770    #[rstest]
771    fn test_atomic_map_default_is_empty() {
772        let map: AtomicMap<u32, u32> = AtomicMap::default();
773        assert!(map.is_empty());
774    }
775
776    #[rstest]
777    fn test_atomic_map_insert_and_get_cloned() {
778        let map = AtomicMap::new();
779        map.insert("a".to_string(), 1);
780        map.insert("b".to_string(), 2);
781
782        assert_eq!(map.get_cloned(&"a".to_string()), Some(1));
783        assert_eq!(map.get_cloned(&"b".to_string()), Some(2));
784        assert_eq!(map.get_cloned(&"c".to_string()), None);
785        assert_eq!(map.len(), 2);
786    }
787
788    #[rstest]
789    fn test_atomic_map_insert_overwrites() {
790        let map = AtomicMap::new();
791        map.insert("key".to_string(), 1);
792        map.insert("key".to_string(), 2);
793
794        assert_eq!(map.get_cloned(&"key".to_string()), Some(2));
795        assert_eq!(map.len(), 1);
796    }
797
798    #[rstest]
799    fn test_atomic_map_contains_key() {
800        let map = AtomicMap::new();
801        map.insert("present".to_string(), 42);
802
803        assert!(map.contains_key(&"present".to_string()));
804        assert!(!map.contains_key(&"absent".to_string()));
805    }
806
807    #[rstest]
808    fn test_atomic_map_remove() {
809        let map = AtomicMap::new();
810        map.insert("a".to_string(), 1);
811        map.insert("b".to_string(), 2);
812        map.remove(&"a".to_string());
813
814        assert!(!map.contains_key(&"a".to_string()));
815        assert!(map.contains_key(&"b".to_string()));
816        assert_eq!(map.len(), 1);
817    }
818
819    #[rstest]
820    fn test_atomic_map_remove_nonexistent() {
821        let map = AtomicMap::new();
822        map.insert("a".to_string(), 1);
823        map.remove(&"z".to_string());
824
825        assert_eq!(map.len(), 1);
826    }
827
828    #[rstest]
829    fn test_atomic_map_store_replaces_contents() {
830        let map = AtomicMap::new();
831        map.insert("old".to_string(), 1);
832
833        let mut replacement = AHashMap::new();
834        replacement.insert("new".to_string(), 99);
835        map.store(replacement);
836
837        assert!(!map.contains_key(&"old".to_string()));
838        assert_eq!(map.get_cloned(&"new".to_string()), Some(99));
839    }
840
841    #[rstest]
842    fn test_atomic_map_store_empty_clears() {
843        let map = AtomicMap::new();
844        map.insert("key".to_string(), 1);
845        map.store(AHashMap::new());
846
847        assert!(map.is_empty());
848    }
849
850    #[rstest]
851    fn test_atomic_map_rcu_batch_insert() {
852        let map = AtomicMap::new();
853        let entries: Vec<(String, i32)> = (0..5).map(|i| (format!("k{i}"), i)).collect();
854
855        map.rcu(|m| {
856            for (k, v) in &entries {
857                m.insert(k.clone(), *v);
858            }
859        });
860
861        assert_eq!(map.len(), 5);
862        for i in 0..5 {
863            assert_eq!(map.get_cloned(&format!("k{i}")), Some(i));
864        }
865    }
866
867    #[rstest]
868    fn test_atomic_map_rcu_mixed_operations() {
869        let map = AtomicMap::new();
870        map.insert("a".to_string(), 1);
871        map.insert("b".to_string(), 2);
872
873        map.rcu(|m| {
874            m.remove(&"a".to_string());
875            m.insert("c".to_string(), 3);
876            if let Some(v) = m.get_mut(&"b".to_string()) {
877                *v = 20;
878            }
879        });
880
881        assert_eq!(map.get_cloned(&"a".to_string()), None);
882        assert_eq!(map.get_cloned(&"b".to_string()), Some(20));
883        assert_eq!(map.get_cloned(&"c".to_string()), Some(3));
884    }
885
886    #[rstest]
887    fn test_atomic_map_load_returns_snapshot() {
888        let map = AtomicMap::new();
889        map.insert("key".to_string(), 42);
890
891        let snapshot = map.load();
892        assert_eq!(snapshot.get(&"key".to_string()), Some(&42));
893    }
894
895    #[rstest]
896    fn test_atomic_map_load_snapshot_not_affected_by_later_writes() {
897        let map = AtomicMap::new();
898        map.insert("a".to_string(), 1);
899
900        let snapshot = map.load();
901        map.insert("b".to_string(), 2);
902
903        assert!(snapshot.get(&"b".to_string()).is_none());
904        assert_eq!(map.get_cloned(&"b".to_string()), Some(2));
905    }
906
907    #[rstest]
908    fn test_atomic_map_from_ahashmap() {
909        let mut source = AHashMap::new();
910        source.insert(1, "one".to_string());
911        source.insert(2, "two".to_string());
912
913        let map = AtomicMap::from(source);
914
915        assert_eq!(map.len(), 2);
916        assert_eq!(map.get_cloned(&1), Some("one".to_string()));
917    }
918
919    #[rstest]
920    fn test_atomic_map_debug() {
921        let map = AtomicMap::new();
922        map.insert("key".to_string(), 42);
923
924        let debug_str = format!("{map:?}");
925        assert!(debug_str.contains("key"));
926        assert!(debug_str.contains("42"));
927    }
928
929    #[rstest]
930    fn test_atomic_map_debug_empty() {
931        let map: AtomicMap<String, i32> = AtomicMap::new();
932        let debug_str = format!("{map:?}");
933        assert_eq!(debug_str, "{}");
934    }
935
936    #[rstest]
937    fn test_atomic_map_load_iteration() {
938        let map = AtomicMap::new();
939        map.insert(1, 10);
940        map.insert(2, 20);
941        map.insert(3, 30);
942
943        let guard = map.load();
944        let mut pairs: Vec<_> = guard.iter().map(|(k, v)| (*k, *v)).collect();
945        pairs.sort_unstable();
946
947        assert_eq!(pairs, vec![(1, 10), (2, 20), (3, 30)]);
948    }
949
950    #[rstest]
951    fn test_atomic_map_concurrent_reads() {
952        let map = Arc::new(AtomicMap::new());
953        for i in 0u32..100 {
954            map.insert(i, i * 10);
955        }
956
957        let barrier = Arc::new(Barrier::new(8));
958        let handles: Vec<_> = (0..8)
959            .map(|_| {
960                let map = Arc::clone(&map);
961                let barrier = Arc::clone(&barrier);
962                std::thread::spawn(move || {
963                    barrier.wait();
964                    for i in 0u32..100 {
965                        assert_eq!(map.get_cloned(&i), Some(i * 10));
966                    }
967                })
968            })
969            .collect();
970
971        for h in handles {
972            h.join().unwrap();
973        }
974    }
975
976    #[rstest]
977    fn test_atomic_map_concurrent_rcu_writes() {
978        let map = Arc::new(AtomicMap::new());
979        let barrier = Arc::new(Barrier::new(4));
980
981        let handles: Vec<_> = (0..4u32)
982            .map(|t| {
983                let map = Arc::clone(&map);
984                let barrier = Arc::clone(&barrier);
985                std::thread::spawn(move || {
986                    barrier.wait();
987                    for i in 0..25 {
988                        let key = t * 25 + i;
989                        map.insert(key, key * 10);
990                    }
991                })
992            })
993            .collect();
994
995        for h in handles {
996            h.join().unwrap();
997        }
998
999        assert_eq!(map.len(), 100);
1000        for i in 0u32..100 {
1001            assert_eq!(map.get_cloned(&i), Some(i * 10), "wrong value for {i}");
1002        }
1003    }
1004
1005    #[rstest]
1006    fn test_atomic_map_concurrent_read_write() {
1007        let map = Arc::new(AtomicMap::new());
1008        for i in 0u32..100 {
1009            map.insert(i, i);
1010        }
1011
1012        let barrier = Arc::new(Barrier::new(5));
1013
1014        let writer = {
1015            let map = Arc::clone(&map);
1016            let barrier = Arc::clone(&barrier);
1017            std::thread::spawn(move || {
1018                barrier.wait();
1019                for i in 100u32..200 {
1020                    map.insert(i, i);
1021                }
1022            })
1023        };
1024
1025        let readers: Vec<_> = (0..4)
1026            .map(|_| {
1027                let map = Arc::clone(&map);
1028                let barrier = Arc::clone(&barrier);
1029                std::thread::spawn(move || {
1030                    barrier.wait();
1031                    for _ in 0..1000 {
1032                        let snapshot = map.load();
1033                        let len = snapshot.len();
1034                        assert!(
1035                            (100..=200).contains(&len),
1036                            "snapshot len {len} outside expected range"
1037                        );
1038                        for i in 0u32..100 {
1039                            assert_eq!(
1040                                snapshot.get(&i).copied(),
1041                                Some(i),
1042                                "original key {i} missing or wrong"
1043                            );
1044                        }
1045                    }
1046                })
1047            })
1048            .collect();
1049
1050        writer.join().unwrap();
1051        for r in readers {
1052            r.join().unwrap();
1053        }
1054
1055        assert_eq!(map.len(), 200);
1056    }
1057
1058    #[rstest]
1059    fn test_atomic_map_snapshot_consistency_under_store() {
1060        let map = Arc::new(AtomicMap::new());
1061        let barrier = Arc::new(Barrier::new(5));
1062
1063        let writer = {
1064            let map = Arc::clone(&map);
1065            let barrier = Arc::clone(&barrier);
1066            std::thread::spawn(move || {
1067                barrier.wait();
1068                for batch in 0u32..50 {
1069                    let start = batch * 10;
1070                    let new_map: AHashMap<u32, u32> =
1071                        (start..start + 10).map(|i| (i, batch)).collect();
1072                    map.store(new_map);
1073                }
1074            })
1075        };
1076
1077        let readers: Vec<_> = (0..4)
1078            .map(|_| {
1079                let map = Arc::clone(&map);
1080                let barrier = Arc::clone(&barrier);
1081                std::thread::spawn(move || {
1082                    barrier.wait();
1083                    for _ in 0..5000 {
1084                        let snapshot = map.load();
1085                        if snapshot.is_empty() {
1086                            continue;
1087                        }
1088                        let values: AHashSet<u32> = snapshot.values().copied().collect();
1089                        assert_eq!(
1090                            values.len(),
1091                            1,
1092                            "snapshot has mixed batch values: {values:?}"
1093                        );
1094                        assert_eq!(snapshot.len(), 10, "partial snapshot");
1095                    }
1096                })
1097            })
1098            .collect();
1099
1100        writer.join().unwrap();
1101        for r in readers {
1102            r.join().unwrap();
1103        }
1104    }
1105
1106    mod proptests {
1107        use proptest::prelude::*;
1108        use rstest::rstest;
1109
1110        use super::*;
1111
1112        #[derive(Debug, Clone)]
1113        enum SetOp {
1114            Insert(u16),
1115            Remove(u16),
1116            Contains(u16),
1117            Len,
1118            IsEmpty,
1119        }
1120
1121        fn set_op_strategy() -> impl Strategy<Value = SetOp> {
1122            prop_oneof![
1123                3 => any::<u16>().prop_map(SetOp::Insert),
1124                3 => any::<u16>().prop_map(SetOp::Remove),
1125                3 => any::<u16>().prop_map(SetOp::Contains),
1126                1 => Just(SetOp::Len),
1127                1 => Just(SetOp::IsEmpty),
1128            ]
1129        }
1130
1131        proptest! {
1132            #![proptest_config(ProptestConfig {
1133                failure_persistence: Some(Box::new(
1134                    proptest::test_runner::FileFailurePersistence::WithSource("atomic_set")
1135                )),
1136                cases: 500,
1137                ..ProptestConfig::default()
1138            })]
1139
1140            /// AtomicSet matches AHashSet behavior for any sequence of ops.
1141            #[rstest]
1142            fn atomic_set_matches_ahashset(ops in proptest::collection::vec(set_op_strategy(), 0..200)) {
1143                let atomic = AtomicSet::new();
1144                let mut reference = AHashSet::new();
1145
1146                for op in &ops {
1147                    match op {
1148                        SetOp::Insert(k) => {
1149                            atomic.insert(*k);
1150                            reference.insert(*k);
1151                        }
1152                        SetOp::Remove(k) => {
1153                            atomic.remove(k);
1154                            reference.remove(k);
1155                        }
1156                        SetOp::Contains(k) => {
1157                            prop_assert_eq!(
1158                                atomic.contains(k),
1159                                reference.contains(k),
1160                                "contains mismatch for key {}", k
1161                            );
1162                        }
1163                        SetOp::Len => {
1164                            prop_assert_eq!(atomic.len(), reference.len());
1165                        }
1166                        SetOp::IsEmpty => {
1167                            prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1168                        }
1169                    }
1170                }
1171
1172                prop_assert_eq!(atomic.len(), reference.len());
1173                prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1174
1175                for k in &reference {
1176                    prop_assert!(atomic.contains(k), "atomic missing key {}", k);
1177                }
1178            }
1179
1180            /// store() followed by reads yields exactly the stored contents.
1181            #[rstest]
1182            fn atomic_set_store_snapshot(items in proptest::collection::vec(any::<u16>(), 0..100)) {
1183                let set = AtomicSet::new();
1184                set.insert(9999);
1185
1186                let expected: AHashSet<u16> = items.iter().copied().collect();
1187                set.store(expected.clone());
1188
1189                prop_assert_eq!(set.len(), expected.len());
1190                prop_assert!(!set.contains(&9999) || expected.contains(&9999));
1191
1192                for k in &expected {
1193                    prop_assert!(set.contains(k));
1194                }
1195            }
1196
1197            /// rcu batch mutation matches sequential application.
1198            #[rstest]
1199            fn atomic_set_rcu_batch(
1200                initial in proptest::collection::vec(any::<u16>(), 0..50),
1201                to_add in proptest::collection::vec(any::<u16>(), 0..50),
1202                to_remove in proptest::collection::vec(any::<u16>(), 0..20),
1203            ) {
1204                let set = AtomicSet::new();
1205                let mut reference = AHashSet::new();
1206
1207                for k in &initial {
1208                    set.insert(*k);
1209                    reference.insert(*k);
1210                }
1211
1212                let to_add_clone = to_add.clone();
1213                let to_remove_clone = to_remove.clone();
1214                set.rcu(|s| {
1215                    for k in &to_add_clone {
1216                        s.insert(*k);
1217                    }
1218                    for k in &to_remove_clone {
1219                        s.remove(k);
1220                    }
1221                });
1222
1223                for k in &to_add {
1224                    reference.insert(*k);
1225                }
1226                for k in &to_remove {
1227                    reference.remove(k);
1228                }
1229
1230                prop_assert_eq!(set.len(), reference.len());
1231                for k in &reference {
1232                    prop_assert!(set.contains(k));
1233                }
1234            }
1235
1236            /// load() returns a frozen snapshot unaffected by subsequent writes.
1237            #[rstest]
1238            fn atomic_set_snapshot_isolation(
1239                initial in proptest::collection::vec(any::<u16>(), 1..50),
1240                extra in proptest::collection::vec(any::<u16>(), 1..50),
1241            ) {
1242                let set = AtomicSet::new();
1243                for k in &initial {
1244                    set.insert(*k);
1245                }
1246
1247                let snapshot = set.load();
1248                let snapshot_contents: AHashSet<u16> = snapshot.iter().copied().collect();
1249
1250                for k in &extra {
1251                    set.insert(*k);
1252                }
1253
1254                let snapshot_after: AHashSet<u16> = snapshot.iter().copied().collect();
1255                prop_assert_eq!(snapshot_contents, snapshot_after, "snapshot mutated after write");
1256            }
1257
1258            /// From<AHashSet> roundtrip: every element in the source is present.
1259            #[rstest]
1260            fn atomic_set_from_roundtrip(items in proptest::collection::vec(any::<u16>(), 0..100)) {
1261                let expected: AHashSet<u16> = items.iter().copied().collect();
1262                let set = AtomicSet::from(expected.clone());
1263
1264                prop_assert_eq!(set.len(), expected.len());
1265                for k in &expected {
1266                    prop_assert!(set.contains(k));
1267                }
1268            }
1269        }
1270
1271        #[derive(Debug, Clone)]
1272        enum MapOp {
1273            Insert(u16, u32),
1274            Remove(u16),
1275            GetCloned(u16),
1276            ContainsKey(u16),
1277            Len,
1278            IsEmpty,
1279            LoadGet(u16),
1280        }
1281
1282        fn map_op_strategy() -> impl Strategy<Value = MapOp> {
1283            prop_oneof![
1284                3 => (any::<u16>(), any::<u32>()).prop_map(|(k, v)| MapOp::Insert(k, v)),
1285                3 => any::<u16>().prop_map(MapOp::Remove),
1286                3 => any::<u16>().prop_map(MapOp::GetCloned),
1287                3 => any::<u16>().prop_map(MapOp::ContainsKey),
1288                1 => Just(MapOp::Len),
1289                1 => Just(MapOp::IsEmpty),
1290                3 => any::<u16>().prop_map(MapOp::LoadGet),
1291            ]
1292        }
1293
1294        proptest! {
1295            #![proptest_config(ProptestConfig {
1296                failure_persistence: Some(Box::new(
1297                    proptest::test_runner::FileFailurePersistence::WithSource("atomic_map")
1298                )),
1299                cases: 500,
1300                ..ProptestConfig::default()
1301            })]
1302
1303            /// AtomicMap matches AHashMap behavior for any sequence of ops.
1304            #[rstest]
1305            fn atomic_map_matches_ahashmap(ops in proptest::collection::vec(map_op_strategy(), 0..200)) {
1306                let atomic = AtomicMap::new();
1307                let mut reference = AHashMap::new();
1308
1309                for op in &ops {
1310                    match op {
1311                        MapOp::Insert(k, v) => {
1312                            atomic.insert(*k, *v);
1313                            reference.insert(*k, *v);
1314                        }
1315                        MapOp::Remove(k) => {
1316                            atomic.remove(k);
1317                            reference.remove(k);
1318                        }
1319                        MapOp::GetCloned(k) => {
1320                            prop_assert_eq!(
1321                                atomic.get_cloned(k),
1322                                reference.get(k).copied(),
1323                                "get_cloned mismatch for key {}", k
1324                            );
1325                        }
1326                        MapOp::ContainsKey(k) => {
1327                            prop_assert_eq!(
1328                                atomic.contains_key(k),
1329                                reference.contains_key(k),
1330                                "contains_key mismatch for key {}", k
1331                            );
1332                        }
1333                        MapOp::Len => {
1334                            prop_assert_eq!(atomic.len(), reference.len());
1335                        }
1336                        MapOp::IsEmpty => {
1337                            prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1338                        }
1339                        MapOp::LoadGet(k) => {
1340                            let snapshot = atomic.load();
1341                            let via_load = snapshot.get(k).copied();
1342                            let via_method = atomic.get_cloned(k);
1343                            prop_assert_eq!(
1344                                via_load,
1345                                reference.get(k).copied(),
1346                                "load().get() mismatch for key {}", k
1347                            );
1348                            prop_assert_eq!(
1349                                via_method,
1350                                reference.get(k).copied(),
1351                                "get_cloned mismatch for key {}", k
1352                            );
1353                        }
1354                    }
1355                }
1356
1357                prop_assert_eq!(atomic.len(), reference.len());
1358                prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1359
1360                for (k, v) in &reference {
1361                    prop_assert_eq!(
1362                        atomic.get_cloned(k),
1363                        Some(*v),
1364                        "value mismatch for key {}", k
1365                    );
1366                }
1367            }
1368
1369            /// store() followed by reads yields exactly the stored contents.
1370            #[rstest]
1371            fn atomic_map_store_snapshot(
1372                items in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..100),
1373            ) {
1374                let map = AtomicMap::new();
1375                map.insert(9999, 0);
1376
1377                let expected: AHashMap<u16, u32> = items.into_iter().collect();
1378                map.store(expected.clone());
1379
1380                prop_assert_eq!(map.len(), expected.len());
1381                prop_assert!(!map.contains_key(&9999) || expected.contains_key(&9999));
1382
1383                for (k, v) in &expected {
1384                    prop_assert_eq!(map.get_cloned(k), Some(*v));
1385                }
1386            }
1387
1388            /// rcu batch mutation matches sequential application.
1389            #[rstest]
1390            fn atomic_map_rcu_batch(
1391                initial in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..50),
1392                to_add in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..50),
1393                to_remove in proptest::collection::vec(any::<u16>(), 0..20),
1394            ) {
1395                let map = AtomicMap::new();
1396                let mut reference = AHashMap::new();
1397
1398                for (k, v) in &initial {
1399                    map.insert(*k, *v);
1400                    reference.insert(*k, *v);
1401                }
1402
1403                let to_add_clone = to_add.clone();
1404                let to_remove_clone = to_remove.clone();
1405                map.rcu(|m| {
1406                    for (k, v) in &to_add_clone {
1407                        m.insert(*k, *v);
1408                    }
1409                    for k in &to_remove_clone {
1410                        m.remove(k);
1411                    }
1412                });
1413
1414                for (k, v) in &to_add {
1415                    reference.insert(*k, *v);
1416                }
1417                for k in &to_remove {
1418                    reference.remove(k);
1419                }
1420
1421                prop_assert_eq!(map.len(), reference.len());
1422                for (k, v) in &reference {
1423                    prop_assert_eq!(map.get_cloned(k), Some(*v));
1424                }
1425            }
1426
1427            /// load() returns a frozen snapshot unaffected by subsequent writes.
1428            #[rstest]
1429            fn atomic_map_snapshot_isolation(
1430                initial in proptest::collection::vec((any::<u16>(), any::<u32>()), 1..50),
1431                extra in proptest::collection::vec((any::<u16>(), any::<u32>()), 1..50),
1432            ) {
1433                let map = AtomicMap::new();
1434                for (k, v) in &initial {
1435                    map.insert(*k, *v);
1436                }
1437
1438                let snapshot = map.load();
1439                let snapshot_contents: AHashMap<u16, u32> =
1440                    snapshot.iter().map(|(k, v)| (*k, *v)).collect();
1441
1442                for (k, v) in &extra {
1443                    map.insert(*k, *v);
1444                }
1445
1446                let snapshot_after: AHashMap<u16, u32> =
1447                    snapshot.iter().map(|(k, v)| (*k, *v)).collect();
1448                prop_assert_eq!(snapshot_contents, snapshot_after, "snapshot mutated after write");
1449            }
1450
1451            /// From<AHashMap> roundtrip: every entry in the source is present.
1452            #[rstest]
1453            fn atomic_map_from_roundtrip(
1454                items in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..100),
1455            ) {
1456                let expected: AHashMap<u16, u32> = items.into_iter().collect();
1457                let map = AtomicMap::from(expected.clone());
1458
1459                prop_assert_eq!(map.len(), expected.len());
1460                for (k, v) in &expected {
1461                    prop_assert_eq!(map.get_cloned(k), Some(*v));
1462                }
1463            }
1464        }
1465    }
1466
1467    #[rstest]
1468    fn test_hashset_setlike() {
1469        let mut set: HashSet<String> = HashSet::new();
1470        set.insert("test".to_string());
1471        set.insert("value".to_string());
1472
1473        assert!(set.contains(&"test".to_string()));
1474        assert!(!set.contains(&"missing".to_string()));
1475        assert!(!set.is_empty());
1476
1477        let empty_set: HashSet<String> = HashSet::new();
1478        assert!(empty_set.is_empty());
1479    }
1480
1481    #[rstest]
1482    fn test_indexset_setlike() {
1483        let mut set: IndexSet<String> = IndexSet::new();
1484        set.insert("test".to_string());
1485        set.insert("value".to_string());
1486
1487        assert!(set.contains(&"test".to_string()));
1488        assert!(!set.contains(&"missing".to_string()));
1489        assert!(!set.is_empty());
1490
1491        let empty_set: IndexSet<String> = IndexSet::new();
1492        assert!(empty_set.is_empty());
1493    }
1494
1495    #[rstest]
1496    fn test_into_ustr_vec_from_strings() {
1497        let items = vec!["foo".to_string(), "bar".to_string()];
1498        let ustrs = super::into_ustr_vec(items);
1499
1500        assert_eq!(ustrs.len(), 2);
1501        assert_eq!(ustrs[0], Ustr::from("foo"));
1502        assert_eq!(ustrs[1], Ustr::from("bar"));
1503    }
1504
1505    #[rstest]
1506    fn test_into_ustr_vec_from_str_slices() {
1507        let items = ["alpha", "beta", "gamma"];
1508        let ustrs = super::into_ustr_vec(items);
1509
1510        assert_eq!(ustrs.len(), 3);
1511        assert_eq!(ustrs[2], Ustr::from("gamma"));
1512    }
1513
1514    #[rstest]
1515    fn test_ahashset_setlike() {
1516        let mut set: AHashSet<String> = AHashSet::new();
1517        set.insert("test".to_string());
1518        set.insert("value".to_string());
1519
1520        assert!(set.contains(&"test".to_string()));
1521        assert!(!set.contains(&"missing".to_string()));
1522        assert!(!set.is_empty());
1523
1524        let empty_set: AHashSet<String> = AHashSet::new();
1525        assert!(empty_set.is_empty());
1526    }
1527
1528    #[rstest]
1529    fn test_hashmap_maplike() {
1530        let mut map: HashMap<String, i32> = HashMap::new();
1531        map.insert("key1".to_string(), 42);
1532        map.insert("key2".to_string(), 100);
1533
1534        assert!(map.contains_key(&"key1".to_string()));
1535        assert!(!map.contains_key(&"missing".to_string()));
1536        assert!(!map.is_empty());
1537
1538        let empty_map: HashMap<String, i32> = HashMap::new();
1539        assert!(empty_map.is_empty());
1540    }
1541
1542    #[rstest]
1543    fn test_indexmap_maplike() {
1544        let mut map: IndexMap<String, i32> = IndexMap::new();
1545        map.insert("key1".to_string(), 42);
1546        map.insert("key2".to_string(), 100);
1547
1548        assert!(map.contains_key(&"key1".to_string()));
1549        assert!(!map.contains_key(&"missing".to_string()));
1550        assert!(!map.is_empty());
1551
1552        let empty_map: IndexMap<String, i32> = IndexMap::new();
1553        assert!(empty_map.is_empty());
1554    }
1555
1556    #[rstest]
1557    fn test_ahashmap_maplike() {
1558        let mut map: AHashMap<String, i32> = AHashMap::new();
1559        map.insert("key1".to_string(), 42);
1560        map.insert("key2".to_string(), 100);
1561
1562        assert!(map.contains_key(&"key1".to_string()));
1563        assert!(!map.contains_key(&"missing".to_string()));
1564        assert!(!map.is_empty());
1565
1566        let empty_map: AHashMap<String, i32> = AHashMap::new();
1567        assert!(empty_map.is_empty());
1568    }
1569
1570    #[rstest]
1571    fn test_trait_object_setlike() {
1572        let mut hashset: HashSet<String> = HashSet::new();
1573        hashset.insert("test".to_string());
1574
1575        let mut indexset: IndexSet<String> = IndexSet::new();
1576        indexset.insert("test".to_string());
1577
1578        let sets: Vec<&dyn SetLike<Item = String>> = vec![&hashset, &indexset];
1579
1580        for set in sets {
1581            assert!(set.contains(&"test".to_string()));
1582            assert!(!set.is_empty());
1583        }
1584    }
1585
1586    #[rstest]
1587    fn test_trait_object_maplike() {
1588        let mut hashmap: HashMap<String, i32> = HashMap::new();
1589        hashmap.insert("key".to_string(), 42);
1590
1591        let mut indexmap: IndexMap<String, i32> = IndexMap::new();
1592        indexmap.insert("key".to_string(), 42);
1593
1594        let maps: Vec<&dyn MapLike<Key = String, Value = i32>> = vec![&hashmap, &indexmap];
1595
1596        for map in maps {
1597            assert!(map.contains_key(&"key".to_string()));
1598            assert!(!map.is_empty());
1599        }
1600    }
1601}