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    #[expect(
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    #[expect(
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        HashSet::contains(self, v)
309    }
310
311    #[inline]
312    fn is_empty(&self) -> bool {
313        HashSet::is_empty(self)
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::contains_key(self, k)
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        HashMap::contains_key(self, k)
382    }
383
384    #[inline]
385    fn is_empty(&self) -> bool {
386        HashMap::is_empty(self)
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    iter.into_iter()
398        .map(|item| Ustr::from(item.as_ref()))
399        .collect()
400}
401
402#[cfg(test)]
403#[expect(
404    clippy::unnecessary_to_owned,
405    reason = "Required for trait bound satisfaction"
406)]
407mod tests {
408    use std::{
409        collections::{HashMap, HashSet},
410        sync::{Arc, Barrier},
411    };
412
413    use ahash::{AHashMap, AHashSet};
414    use indexmap::{IndexMap, IndexSet};
415    use rstest::*;
416    use ustr::Ustr;
417
418    use super::*;
419
420    #[rstest]
421    fn test_atomic_set_new_is_empty() {
422        let set: AtomicSet<String> = AtomicSet::new();
423        assert!(set.is_empty());
424        assert_eq!(set.len(), 0);
425    }
426
427    #[rstest]
428    fn test_atomic_set_default_is_empty() {
429        let set: AtomicSet<u64> = AtomicSet::default();
430        assert!(set.is_empty());
431    }
432
433    #[rstest]
434    fn test_atomic_set_insert_and_contains() {
435        let set = AtomicSet::new();
436        set.insert(1);
437        set.insert(2);
438
439        assert!(set.contains(&1));
440        assert!(set.contains(&2));
441        assert!(!set.contains(&3));
442        assert_eq!(set.len(), 2);
443    }
444
445    #[rstest]
446    fn test_atomic_set_insert_duplicate() {
447        let set = AtomicSet::new();
448        set.insert(1);
449        set.insert(1);
450
451        assert_eq!(set.len(), 1);
452        assert!(set.contains(&1));
453    }
454
455    #[rstest]
456    fn test_atomic_set_remove() {
457        let set = AtomicSet::new();
458        set.insert(1);
459        set.insert(2);
460        set.remove(&1);
461
462        assert!(!set.contains(&1));
463        assert!(set.contains(&2));
464        assert_eq!(set.len(), 1);
465    }
466
467    #[rstest]
468    fn test_atomic_set_remove_nonexistent() {
469        let set: AtomicSet<i32> = AtomicSet::new();
470        set.insert(1);
471        set.remove(&999);
472
473        assert_eq!(set.len(), 1);
474        assert!(set.contains(&1));
475    }
476
477    #[rstest]
478    fn test_atomic_set_store_replaces_contents() {
479        let set = AtomicSet::new();
480        set.insert(1);
481        set.insert(2);
482
483        let mut replacement = AHashSet::new();
484        replacement.insert(10);
485        replacement.insert(20);
486        set.store(replacement);
487
488        assert!(!set.contains(&1));
489        assert!(!set.contains(&2));
490        assert!(set.contains(&10));
491        assert!(set.contains(&20));
492        assert_eq!(set.len(), 2);
493    }
494
495    #[rstest]
496    fn test_atomic_set_store_empty_clears() {
497        let set = AtomicSet::new();
498        set.insert(1);
499        set.store(AHashSet::new());
500
501        assert!(set.is_empty());
502    }
503
504    #[rstest]
505    fn test_atomic_set_rcu_batch_insert() {
506        let set = AtomicSet::new();
507        set.rcu(|s| {
508            s.insert(1);
509            s.insert(2);
510            s.insert(3);
511        });
512
513        assert_eq!(set.len(), 3);
514        assert!(set.contains(&1));
515        assert!(set.contains(&2));
516        assert!(set.contains(&3));
517    }
518
519    #[rstest]
520    fn test_atomic_set_rcu_mixed_operations() {
521        let set = AtomicSet::new();
522        set.insert(1);
523        set.insert(2);
524
525        set.rcu(|s| {
526            s.remove(&1);
527            s.insert(3);
528        });
529
530        assert!(!set.contains(&1));
531        assert!(set.contains(&2));
532        assert!(set.contains(&3));
533    }
534
535    #[rstest]
536    fn test_atomic_set_load_returns_snapshot() {
537        let set = AtomicSet::new();
538        set.insert(1);
539
540        let snapshot = set.load();
541        assert!(snapshot.contains(&1));
542        assert_eq!(snapshot.len(), 1);
543    }
544
545    #[rstest]
546    fn test_atomic_set_load_snapshot_not_affected_by_later_writes() {
547        let set = AtomicSet::new();
548        set.insert(1);
549
550        let snapshot = set.load();
551        set.insert(2);
552
553        assert!(!snapshot.contains(&2));
554        assert!(set.contains(&2));
555    }
556
557    #[rstest]
558    fn test_atomic_set_from_ahashset() {
559        let mut source = AHashSet::new();
560        source.insert("a".to_string());
561        source.insert("b".to_string());
562
563        let set = AtomicSet::from(source);
564
565        assert_eq!(set.len(), 2);
566        assert!(set.contains(&"a".to_string()));
567        assert!(set.contains(&"b".to_string()));
568    }
569
570    #[rstest]
571    fn test_atomic_set_debug() {
572        let set = AtomicSet::new();
573        set.insert(42);
574
575        let debug_str = format!("{set:?}");
576        assert!(debug_str.contains("42"));
577    }
578
579    #[rstest]
580    fn test_atomic_set_debug_empty() {
581        let set: AtomicSet<i32> = AtomicSet::new();
582        let debug_str = format!("{set:?}");
583        assert_eq!(debug_str, "{}");
584    }
585
586    #[rstest]
587    fn test_atomic_set_load_iteration() {
588        let set = AtomicSet::new();
589        set.insert(1);
590        set.insert(2);
591        set.insert(3);
592
593        let guard = set.load();
594        let mut values: Vec<_> = guard.iter().copied().collect();
595        values.sort_unstable();
596
597        assert_eq!(values, vec![1, 2, 3]);
598    }
599
600    #[rstest]
601    fn test_atomic_set_concurrent_reads() {
602        let set = Arc::new(AtomicSet::new());
603        for i in 0..100 {
604            set.insert(i);
605        }
606
607        let barrier = Arc::new(Barrier::new(8));
608        let handles: Vec<_> = (0..8)
609            .map(|_| {
610                let set = Arc::clone(&set);
611                let barrier = Arc::clone(&barrier);
612                std::thread::spawn(move || {
613                    barrier.wait();
614
615                    for i in 0..100 {
616                        assert!(set.contains(&i));
617                    }
618                })
619            })
620            .collect();
621
622        for h in handles {
623            h.join().unwrap();
624        }
625    }
626
627    #[rstest]
628    fn test_atomic_set_concurrent_rcu_writes() {
629        let set = Arc::new(AtomicSet::new());
630        let barrier = Arc::new(Barrier::new(4));
631
632        let handles: Vec<_> = (0..4u32)
633            .map(|t| {
634                let set = Arc::clone(&set);
635                let barrier = Arc::clone(&barrier);
636                std::thread::spawn(move || {
637                    barrier.wait();
638
639                    for i in 0..25 {
640                        set.insert(t * 25 + i);
641                    }
642                })
643            })
644            .collect();
645
646        for h in handles {
647            h.join().unwrap();
648        }
649
650        assert_eq!(set.len(), 100);
651        for i in 0..100 {
652            assert!(set.contains(&i), "missing {i}");
653        }
654    }
655
656    #[rstest]
657    fn test_atomic_set_concurrent_read_write() {
658        let set = Arc::new(AtomicSet::new());
659        for i in 0u32..100 {
660            set.insert(i);
661        }
662
663        let barrier = Arc::new(Barrier::new(5));
664
665        let writer = {
666            let set = Arc::clone(&set);
667            let barrier = Arc::clone(&barrier);
668            std::thread::spawn(move || {
669                barrier.wait();
670
671                for i in 100u32..200 {
672                    set.insert(i);
673                }
674            })
675        };
676
677        let readers: Vec<_> = (0..4)
678            .map(|_| {
679                let set = Arc::clone(&set);
680                let barrier = Arc::clone(&barrier);
681                std::thread::spawn(move || {
682                    barrier.wait();
683
684                    for _ in 0..1000 {
685                        let snapshot = set.load();
686                        let len = snapshot.len();
687                        assert!(
688                            (100..=200).contains(&len),
689                            "snapshot len {len} outside expected range"
690                        );
691
692                        for i in 0u32..100 {
693                            assert!(snapshot.contains(&i), "original key {i} missing");
694                        }
695                    }
696                })
697            })
698            .collect();
699
700        writer.join().unwrap();
701        for r in readers {
702            r.join().unwrap();
703        }
704
705        assert_eq!(set.len(), 200);
706    }
707
708    #[rstest]
709    fn test_atomic_set_snapshot_consistency_under_store() {
710        let set = Arc::new(AtomicSet::new());
711        let barrier = Arc::new(Barrier::new(5));
712
713        let writer = {
714            let set = Arc::clone(&set);
715            let barrier = Arc::clone(&barrier);
716            std::thread::spawn(move || {
717                barrier.wait();
718
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
734                    for _ in 0..5000 {
735                        let snapshot = set.load();
736                        let items: Vec<u32> = snapshot.iter().copied().collect();
737                        if items.is_empty() {
738                            continue;
739                        }
740                        assert_eq!(
741                            items.len(),
742                            10,
743                            "partial snapshot: got {} items: {items:?}",
744                            items.len()
745                        );
746                        let min = *items.iter().min().unwrap();
747                        let max = *items.iter().max().unwrap();
748                        assert_eq!(
749                            max - min,
750                            9,
751                            "snapshot not from single batch: min={min} max={max}"
752                        );
753                    }
754                })
755            })
756            .collect();
757
758        writer.join().unwrap();
759        for r in readers {
760            r.join().unwrap();
761        }
762    }
763
764    #[rstest]
765    fn test_atomic_map_new_is_empty() {
766        let map: AtomicMap<String, i32> = AtomicMap::new();
767        assert!(map.is_empty());
768        assert_eq!(map.len(), 0);
769    }
770
771    #[rstest]
772    fn test_atomic_map_default_is_empty() {
773        let map: AtomicMap<u32, u32> = AtomicMap::default();
774        assert!(map.is_empty());
775    }
776
777    #[rstest]
778    fn test_atomic_map_insert_and_get_cloned() {
779        let map = AtomicMap::new();
780        map.insert("a".to_string(), 1);
781        map.insert("b".to_string(), 2);
782
783        assert_eq!(map.get_cloned(&"a".to_string()), Some(1));
784        assert_eq!(map.get_cloned(&"b".to_string()), Some(2));
785        assert_eq!(map.get_cloned(&"c".to_string()), None);
786        assert_eq!(map.len(), 2);
787    }
788
789    #[rstest]
790    fn test_atomic_map_insert_overwrites() {
791        let map = AtomicMap::new();
792        map.insert("key".to_string(), 1);
793        map.insert("key".to_string(), 2);
794
795        assert_eq!(map.get_cloned(&"key".to_string()), Some(2));
796        assert_eq!(map.len(), 1);
797    }
798
799    #[rstest]
800    fn test_atomic_map_contains_key() {
801        let map = AtomicMap::new();
802        map.insert("present".to_string(), 42);
803
804        assert!(map.contains_key(&"present".to_string()));
805        assert!(!map.contains_key(&"absent".to_string()));
806    }
807
808    #[rstest]
809    fn test_atomic_map_remove() {
810        let map = AtomicMap::new();
811        map.insert("a".to_string(), 1);
812        map.insert("b".to_string(), 2);
813        map.remove(&"a".to_string());
814
815        assert!(!map.contains_key(&"a".to_string()));
816        assert!(map.contains_key(&"b".to_string()));
817        assert_eq!(map.len(), 1);
818    }
819
820    #[rstest]
821    fn test_atomic_map_remove_nonexistent() {
822        let map = AtomicMap::new();
823        map.insert("a".to_string(), 1);
824        map.remove(&"z".to_string());
825
826        assert_eq!(map.len(), 1);
827    }
828
829    #[rstest]
830    fn test_atomic_map_store_replaces_contents() {
831        let map = AtomicMap::new();
832        map.insert("old".to_string(), 1);
833
834        let mut replacement = AHashMap::new();
835        replacement.insert("new".to_string(), 99);
836        map.store(replacement);
837
838        assert!(!map.contains_key(&"old".to_string()));
839        assert_eq!(map.get_cloned(&"new".to_string()), Some(99));
840    }
841
842    #[rstest]
843    fn test_atomic_map_store_empty_clears() {
844        let map = AtomicMap::new();
845        map.insert("key".to_string(), 1);
846        map.store(AHashMap::new());
847
848        assert!(map.is_empty());
849    }
850
851    #[rstest]
852    fn test_atomic_map_rcu_batch_insert() {
853        let map = AtomicMap::new();
854        let entries: Vec<(String, i32)> = (0..5).map(|i| (format!("k{i}"), i)).collect();
855
856        map.rcu(|m| {
857            for (k, v) in &entries {
858                m.insert(k.clone(), *v);
859            }
860        });
861
862        assert_eq!(map.len(), 5);
863        for i in 0..5 {
864            assert_eq!(map.get_cloned(&format!("k{i}")), Some(i));
865        }
866    }
867
868    #[rstest]
869    fn test_atomic_map_rcu_mixed_operations() {
870        let map = AtomicMap::new();
871        map.insert("a".to_string(), 1);
872        map.insert("b".to_string(), 2);
873
874        map.rcu(|m| {
875            m.remove(&"a".to_string());
876            m.insert("c".to_string(), 3);
877            if let Some(v) = m.get_mut(&"b".to_string()) {
878                *v = 20;
879            }
880        });
881
882        assert_eq!(map.get_cloned(&"a".to_string()), None);
883        assert_eq!(map.get_cloned(&"b".to_string()), Some(20));
884        assert_eq!(map.get_cloned(&"c".to_string()), Some(3));
885    }
886
887    #[rstest]
888    fn test_atomic_map_load_returns_snapshot() {
889        let map = AtomicMap::new();
890        map.insert("key".to_string(), 42);
891
892        let snapshot = map.load();
893        assert_eq!(snapshot.get(&"key".to_string()), Some(&42));
894    }
895
896    #[rstest]
897    fn test_atomic_map_load_snapshot_not_affected_by_later_writes() {
898        let map = AtomicMap::new();
899        map.insert("a".to_string(), 1);
900
901        let snapshot = map.load();
902        map.insert("b".to_string(), 2);
903
904        assert!(snapshot.get(&"b".to_string()).is_none());
905        assert_eq!(map.get_cloned(&"b".to_string()), Some(2));
906    }
907
908    #[rstest]
909    fn test_atomic_map_from_ahashmap() {
910        let mut source = AHashMap::new();
911        source.insert(1, "one".to_string());
912        source.insert(2, "two".to_string());
913
914        let map = AtomicMap::from(source);
915
916        assert_eq!(map.len(), 2);
917        assert_eq!(map.get_cloned(&1), Some("one".to_string()));
918    }
919
920    #[rstest]
921    fn test_atomic_map_debug() {
922        let map = AtomicMap::new();
923        map.insert("key".to_string(), 42);
924
925        let debug_str = format!("{map:?}");
926        assert!(debug_str.contains("key"));
927        assert!(debug_str.contains("42"));
928    }
929
930    #[rstest]
931    fn test_atomic_map_debug_empty() {
932        let map: AtomicMap<String, i32> = AtomicMap::new();
933        let debug_str = format!("{map:?}");
934        assert_eq!(debug_str, "{}");
935    }
936
937    #[rstest]
938    fn test_atomic_map_load_iteration() {
939        let map = AtomicMap::new();
940        map.insert(1, 10);
941        map.insert(2, 20);
942        map.insert(3, 30);
943
944        let guard = map.load();
945        let mut pairs: Vec<_> = guard.iter().map(|(k, v)| (*k, *v)).collect();
946        pairs.sort_unstable();
947
948        assert_eq!(pairs, vec![(1, 10), (2, 20), (3, 30)]);
949    }
950
951    #[rstest]
952    fn test_atomic_map_concurrent_reads() {
953        let map = Arc::new(AtomicMap::new());
954        for i in 0u32..100 {
955            map.insert(i, i * 10);
956        }
957
958        let barrier = Arc::new(Barrier::new(8));
959        let handles: Vec<_> = (0..8)
960            .map(|_| {
961                let map = Arc::clone(&map);
962                let barrier = Arc::clone(&barrier);
963                std::thread::spawn(move || {
964                    barrier.wait();
965
966                    for i in 0u32..100 {
967                        assert_eq!(map.get_cloned(&i), Some(i * 10));
968                    }
969                })
970            })
971            .collect();
972
973        for h in handles {
974            h.join().unwrap();
975        }
976    }
977
978    #[rstest]
979    fn test_atomic_map_concurrent_rcu_writes() {
980        let map = Arc::new(AtomicMap::new());
981        let barrier = Arc::new(Barrier::new(4));
982
983        let handles: Vec<_> = (0..4u32)
984            .map(|t| {
985                let map = Arc::clone(&map);
986                let barrier = Arc::clone(&barrier);
987                std::thread::spawn(move || {
988                    barrier.wait();
989
990                    for i in 0..25 {
991                        let key = t * 25 + i;
992                        map.insert(key, key * 10);
993                    }
994                })
995            })
996            .collect();
997
998        for h in handles {
999            h.join().unwrap();
1000        }
1001
1002        assert_eq!(map.len(), 100);
1003        for i in 0u32..100 {
1004            assert_eq!(map.get_cloned(&i), Some(i * 10), "wrong value for {i}");
1005        }
1006    }
1007
1008    #[rstest]
1009    fn test_atomic_map_concurrent_read_write() {
1010        let map = Arc::new(AtomicMap::new());
1011        for i in 0u32..100 {
1012            map.insert(i, i);
1013        }
1014
1015        let barrier = Arc::new(Barrier::new(5));
1016
1017        let writer = {
1018            let map = Arc::clone(&map);
1019            let barrier = Arc::clone(&barrier);
1020            std::thread::spawn(move || {
1021                barrier.wait();
1022
1023                for i in 100u32..200 {
1024                    map.insert(i, i);
1025                }
1026            })
1027        };
1028
1029        let readers: Vec<_> = (0..4)
1030            .map(|_| {
1031                let map = Arc::clone(&map);
1032                let barrier = Arc::clone(&barrier);
1033                std::thread::spawn(move || {
1034                    barrier.wait();
1035
1036                    for _ in 0..1000 {
1037                        let snapshot = map.load();
1038                        let len = snapshot.len();
1039                        assert!(
1040                            (100..=200).contains(&len),
1041                            "snapshot len {len} outside expected range"
1042                        );
1043
1044                        for i in 0u32..100 {
1045                            assert_eq!(
1046                                snapshot.get(&i).copied(),
1047                                Some(i),
1048                                "original key {i} missing or wrong"
1049                            );
1050                        }
1051                    }
1052                })
1053            })
1054            .collect();
1055
1056        writer.join().unwrap();
1057        for r in readers {
1058            r.join().unwrap();
1059        }
1060
1061        assert_eq!(map.len(), 200);
1062    }
1063
1064    #[rstest]
1065    fn test_atomic_map_snapshot_consistency_under_store() {
1066        let map = Arc::new(AtomicMap::new());
1067        let barrier = Arc::new(Barrier::new(5));
1068
1069        let writer = {
1070            let map = Arc::clone(&map);
1071            let barrier = Arc::clone(&barrier);
1072            std::thread::spawn(move || {
1073                barrier.wait();
1074
1075                for batch in 0u32..50 {
1076                    let start = batch * 10;
1077                    let new_map: AHashMap<u32, u32> =
1078                        (start..start + 10).map(|i| (i, batch)).collect();
1079                    map.store(new_map);
1080                }
1081            })
1082        };
1083
1084        let readers: Vec<_> = (0..4)
1085            .map(|_| {
1086                let map = Arc::clone(&map);
1087                let barrier = Arc::clone(&barrier);
1088                std::thread::spawn(move || {
1089                    barrier.wait();
1090
1091                    for _ in 0..5000 {
1092                        let snapshot = map.load();
1093                        if snapshot.is_empty() {
1094                            continue;
1095                        }
1096                        let values: AHashSet<u32> = snapshot.values().copied().collect();
1097                        assert_eq!(
1098                            values.len(),
1099                            1,
1100                            "snapshot has mixed batch values: {values:?}"
1101                        );
1102                        assert_eq!(snapshot.len(), 10, "partial snapshot");
1103                    }
1104                })
1105            })
1106            .collect();
1107
1108        writer.join().unwrap();
1109        for r in readers {
1110            r.join().unwrap();
1111        }
1112    }
1113
1114    mod proptests {
1115        use proptest::prelude::*;
1116        use rstest::rstest;
1117
1118        use super::*;
1119
1120        #[derive(Debug, Clone)]
1121        enum SetOp {
1122            Insert(u16),
1123            Remove(u16),
1124            Contains(u16),
1125            Len,
1126            IsEmpty,
1127        }
1128
1129        fn set_op_strategy() -> impl Strategy<Value = SetOp> {
1130            prop_oneof![
1131                3 => any::<u16>().prop_map(SetOp::Insert),
1132                3 => any::<u16>().prop_map(SetOp::Remove),
1133                3 => any::<u16>().prop_map(SetOp::Contains),
1134                1 => Just(SetOp::Len),
1135                1 => Just(SetOp::IsEmpty),
1136            ]
1137        }
1138
1139        proptest! {
1140            #![proptest_config(ProptestConfig {
1141                failure_persistence: Some(Box::new(
1142                    proptest::test_runner::FileFailurePersistence::WithSource("atomic_set")
1143                )),
1144                cases: 500,
1145                ..ProptestConfig::default()
1146            })]
1147
1148            /// AtomicSet matches AHashSet behavior for any sequence of ops.
1149            #[rstest]
1150            fn atomic_set_matches_ahashset(ops in proptest::collection::vec(set_op_strategy(), 0..200)) {
1151                let atomic = AtomicSet::new();
1152                let mut reference = AHashSet::new();
1153
1154                for op in &ops {
1155                    match op {
1156                        SetOp::Insert(k) => {
1157                            atomic.insert(*k);
1158                            reference.insert(*k);
1159                        }
1160                        SetOp::Remove(k) => {
1161                            atomic.remove(k);
1162                            reference.remove(k);
1163                        }
1164                        SetOp::Contains(k) => {
1165                            prop_assert_eq!(
1166                                atomic.contains(k),
1167                                reference.contains(k),
1168                                "contains mismatch for key {}", k
1169                            );
1170                        }
1171                        SetOp::Len => {
1172                            prop_assert_eq!(atomic.len(), reference.len());
1173                        }
1174                        SetOp::IsEmpty => {
1175                            prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1176                        }
1177                    }
1178                }
1179
1180                prop_assert_eq!(atomic.len(), reference.len());
1181                prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1182
1183                for k in &reference {
1184                    prop_assert!(atomic.contains(k), "atomic missing key {}", k);
1185                }
1186            }
1187
1188            /// store() followed by reads yields exactly the stored contents.
1189            #[rstest]
1190            fn atomic_set_store_snapshot(items in proptest::collection::vec(any::<u16>(), 0..100)) {
1191                let set = AtomicSet::new();
1192                set.insert(9999);
1193
1194                let expected: AHashSet<u16> = items.iter().copied().collect();
1195                set.store(expected.clone());
1196
1197                prop_assert_eq!(set.len(), expected.len());
1198                prop_assert!(!set.contains(&9999) || expected.contains(&9999));
1199
1200                for k in &expected {
1201                    prop_assert!(set.contains(k));
1202                }
1203            }
1204
1205            /// rcu batch mutation matches sequential application.
1206            #[rstest]
1207            fn atomic_set_rcu_batch(
1208                initial in proptest::collection::vec(any::<u16>(), 0..50),
1209                to_add in proptest::collection::vec(any::<u16>(), 0..50),
1210                to_remove in proptest::collection::vec(any::<u16>(), 0..20),
1211            ) {
1212                let set = AtomicSet::new();
1213                let mut reference = AHashSet::new();
1214
1215                for k in &initial {
1216                    set.insert(*k);
1217                    reference.insert(*k);
1218                }
1219
1220                let to_add_clone = to_add.clone();
1221                let to_remove_clone = to_remove.clone();
1222                set.rcu(|s| {
1223                    for k in &to_add_clone {
1224                        s.insert(*k);
1225                    }
1226
1227                    for k in &to_remove_clone {
1228                        s.remove(k);
1229                    }
1230                });
1231
1232                for k in &to_add {
1233                    reference.insert(*k);
1234                }
1235
1236                for k in &to_remove {
1237                    reference.remove(k);
1238                }
1239
1240                prop_assert_eq!(set.len(), reference.len());
1241                for k in &reference {
1242                    prop_assert!(set.contains(k));
1243                }
1244            }
1245
1246            /// load() returns a frozen snapshot unaffected by subsequent writes.
1247            #[rstest]
1248            fn atomic_set_snapshot_isolation(
1249                initial in proptest::collection::vec(any::<u16>(), 1..50),
1250                extra in proptest::collection::vec(any::<u16>(), 1..50),
1251            ) {
1252                let set = AtomicSet::new();
1253                let expected: AHashSet<u16> = initial.iter().copied().collect();
1254                for k in &initial {
1255                    set.insert(*k);
1256                }
1257
1258                let snapshot = set.load();
1259                let snapshot_contents: AHashSet<u16> = snapshot.iter().copied().collect();
1260                prop_assert_eq!(&snapshot_contents, &expected);
1261
1262                for k in &extra {
1263                    set.insert(*k);
1264                }
1265
1266                let snapshot_after: AHashSet<u16> = snapshot.iter().copied().collect();
1267                prop_assert_eq!(&snapshot_contents, &snapshot_after, "snapshot mutated after write");
1268            }
1269
1270            /// From<AHashSet> roundtrip: every element in the source is present.
1271            #[rstest]
1272            fn atomic_set_from_roundtrip(items in proptest::collection::vec(any::<u16>(), 0..100)) {
1273                let expected: AHashSet<u16> = items.iter().copied().collect();
1274                let set = AtomicSet::from(expected.clone());
1275
1276                prop_assert_eq!(set.len(), expected.len());
1277                for k in &expected {
1278                    prop_assert!(set.contains(k));
1279                }
1280            }
1281        }
1282
1283        #[derive(Debug, Clone)]
1284        enum MapOp {
1285            Insert(u16, u32),
1286            Remove(u16),
1287            GetCloned(u16),
1288            ContainsKey(u16),
1289            Len,
1290            IsEmpty,
1291            LoadGet(u16),
1292        }
1293
1294        fn map_op_strategy() -> impl Strategy<Value = MapOp> {
1295            prop_oneof![
1296                3 => (any::<u16>(), any::<u32>()).prop_map(|(k, v)| MapOp::Insert(k, v)),
1297                3 => any::<u16>().prop_map(MapOp::Remove),
1298                3 => any::<u16>().prop_map(MapOp::GetCloned),
1299                3 => any::<u16>().prop_map(MapOp::ContainsKey),
1300                1 => Just(MapOp::Len),
1301                1 => Just(MapOp::IsEmpty),
1302                3 => any::<u16>().prop_map(MapOp::LoadGet),
1303            ]
1304        }
1305
1306        proptest! {
1307            #![proptest_config(ProptestConfig {
1308                failure_persistence: Some(Box::new(
1309                    proptest::test_runner::FileFailurePersistence::WithSource("atomic_map")
1310                )),
1311                cases: 500,
1312                ..ProptestConfig::default()
1313            })]
1314
1315            /// AtomicMap matches AHashMap behavior for any sequence of ops.
1316            #[rstest]
1317            fn atomic_map_matches_ahashmap(ops in proptest::collection::vec(map_op_strategy(), 0..200)) {
1318                let atomic = AtomicMap::new();
1319                let mut reference = AHashMap::new();
1320
1321                for op in &ops {
1322                    match op {
1323                        MapOp::Insert(k, v) => {
1324                            atomic.insert(*k, *v);
1325                            reference.insert(*k, *v);
1326                        }
1327                        MapOp::Remove(k) => {
1328                            atomic.remove(k);
1329                            reference.remove(k);
1330                        }
1331                        MapOp::GetCloned(k) => {
1332                            prop_assert_eq!(
1333                                atomic.get_cloned(k),
1334                                reference.get(k).copied(),
1335                                "get_cloned mismatch for key {}", k
1336                            );
1337                        }
1338                        MapOp::ContainsKey(k) => {
1339                            prop_assert_eq!(
1340                                atomic.contains_key(k),
1341                                reference.contains_key(k),
1342                                "contains_key mismatch for key {}", k
1343                            );
1344                        }
1345                        MapOp::Len => {
1346                            prop_assert_eq!(atomic.len(), reference.len());
1347                        }
1348                        MapOp::IsEmpty => {
1349                            prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1350                        }
1351                        MapOp::LoadGet(k) => {
1352                            let snapshot = atomic.load();
1353                            let via_load = snapshot.get(k).copied();
1354                            let via_method = atomic.get_cloned(k);
1355                            prop_assert_eq!(
1356                                via_load,
1357                                reference.get(k).copied(),
1358                                "load().get() mismatch for key {}", k
1359                            );
1360                            prop_assert_eq!(
1361                                via_method,
1362                                reference.get(k).copied(),
1363                                "get_cloned mismatch for key {}", k
1364                            );
1365                        }
1366                    }
1367                }
1368
1369                prop_assert_eq!(atomic.len(), reference.len());
1370                prop_assert_eq!(atomic.is_empty(), reference.is_empty());
1371
1372                for (k, v) in &reference {
1373                    prop_assert_eq!(
1374                        atomic.get_cloned(k),
1375                        Some(*v),
1376                        "value mismatch for key {}", k
1377                    );
1378                }
1379            }
1380
1381            /// store() followed by reads yields exactly the stored contents.
1382            #[rstest]
1383            fn atomic_map_store_snapshot(
1384                items in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..100),
1385            ) {
1386                let map = AtomicMap::new();
1387                map.insert(9999, 0);
1388
1389                let expected: AHashMap<u16, u32> = items.into_iter().collect();
1390                map.store(expected.clone());
1391
1392                prop_assert_eq!(map.len(), expected.len());
1393                prop_assert!(!map.contains_key(&9999) || expected.contains_key(&9999));
1394
1395                for (k, v) in &expected {
1396                    prop_assert_eq!(map.get_cloned(k), Some(*v));
1397                }
1398            }
1399
1400            /// rcu batch mutation matches sequential application.
1401            #[rstest]
1402            fn atomic_map_rcu_batch(
1403                initial in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..50),
1404                to_add in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..50),
1405                to_remove in proptest::collection::vec(any::<u16>(), 0..20),
1406            ) {
1407                let map = AtomicMap::new();
1408                let mut reference = AHashMap::new();
1409
1410                for (k, v) in &initial {
1411                    map.insert(*k, *v);
1412                    reference.insert(*k, *v);
1413                }
1414
1415                let to_add_clone = to_add.clone();
1416                let to_remove_clone = to_remove.clone();
1417                map.rcu(|m| {
1418                    for (k, v) in &to_add_clone {
1419                        m.insert(*k, *v);
1420                    }
1421
1422                    for k in &to_remove_clone {
1423                        m.remove(k);
1424                    }
1425                });
1426
1427                for (k, v) in &to_add {
1428                    reference.insert(*k, *v);
1429                }
1430
1431                for k in &to_remove {
1432                    reference.remove(k);
1433                }
1434
1435                prop_assert_eq!(map.len(), reference.len());
1436                for (k, v) in &reference {
1437                    prop_assert_eq!(map.get_cloned(k), Some(*v));
1438                }
1439            }
1440
1441            /// load() returns a frozen snapshot unaffected by subsequent writes.
1442            #[rstest]
1443            fn atomic_map_snapshot_isolation(
1444                initial in proptest::collection::vec((any::<u16>(), any::<u32>()), 1..50),
1445                extra in proptest::collection::vec((any::<u16>(), any::<u32>()), 1..50),
1446            ) {
1447                let map = AtomicMap::new();
1448                let expected: AHashMap<u16, u32> = initial.iter().copied().collect();
1449                for (k, v) in &initial {
1450                    map.insert(*k, *v);
1451                }
1452
1453                let snapshot = map.load();
1454                let snapshot_contents: AHashMap<u16, u32> =
1455                    snapshot.iter().map(|(k, v)| (*k, *v)).collect();
1456                prop_assert_eq!(&snapshot_contents, &expected);
1457
1458                for (k, v) in &extra {
1459                    map.insert(*k, *v);
1460                }
1461
1462                let snapshot_after: AHashMap<u16, u32> =
1463                    snapshot.iter().map(|(k, v)| (*k, *v)).collect();
1464                prop_assert_eq!(&snapshot_contents, &snapshot_after, "snapshot mutated after write");
1465            }
1466
1467            /// From<AHashMap> roundtrip: every entry in the source is present.
1468            #[rstest]
1469            fn atomic_map_from_roundtrip(
1470                items in proptest::collection::vec((any::<u16>(), any::<u32>()), 0..100),
1471            ) {
1472                let expected: AHashMap<u16, u32> = items.into_iter().collect();
1473                let map = AtomicMap::from(expected.clone());
1474
1475                prop_assert_eq!(map.len(), expected.len());
1476                for (k, v) in &expected {
1477                    prop_assert_eq!(map.get_cloned(k), Some(*v));
1478                }
1479            }
1480        }
1481    }
1482
1483    #[rstest]
1484    fn test_hashset_setlike() {
1485        let mut set: HashSet<String> = HashSet::new();
1486        set.insert("test".to_string());
1487        set.insert("value".to_string());
1488
1489        assert!(set.contains(&"test".to_string()));
1490        assert!(!set.contains(&"missing".to_string()));
1491        assert!(!set.is_empty());
1492
1493        let empty_set: HashSet<String> = HashSet::new();
1494        assert!(empty_set.is_empty());
1495    }
1496
1497    #[rstest]
1498    fn test_indexset_setlike() {
1499        let mut set: IndexSet<String> = IndexSet::new();
1500        set.insert("test".to_string());
1501        set.insert("value".to_string());
1502
1503        assert!(set.contains(&"test".to_string()));
1504        assert!(!set.contains(&"missing".to_string()));
1505        assert!(!set.is_empty());
1506
1507        let empty_set: IndexSet<String> = IndexSet::new();
1508        assert!(empty_set.is_empty());
1509    }
1510
1511    #[rstest]
1512    fn test_into_ustr_vec_from_strings() {
1513        let items = vec!["foo".to_string(), "bar".to_string()];
1514        let ustrs = super::into_ustr_vec(items);
1515
1516        assert_eq!(ustrs.len(), 2);
1517        assert_eq!(ustrs[0], Ustr::from("foo"));
1518        assert_eq!(ustrs[1], Ustr::from("bar"));
1519    }
1520
1521    #[rstest]
1522    fn test_into_ustr_vec_from_str_slices() {
1523        let items = ["alpha", "beta", "gamma"];
1524        let ustrs = super::into_ustr_vec(items);
1525
1526        assert_eq!(ustrs.len(), 3);
1527        assert_eq!(ustrs[2], Ustr::from("gamma"));
1528    }
1529
1530    #[rstest]
1531    fn test_ahashset_setlike() {
1532        let mut set: AHashSet<String> = AHashSet::new();
1533        set.insert("test".to_string());
1534        set.insert("value".to_string());
1535
1536        assert!(set.contains(&"test".to_string()));
1537        assert!(!set.contains(&"missing".to_string()));
1538        assert!(!set.is_empty());
1539
1540        let empty_set: AHashSet<String> = AHashSet::new();
1541        assert!(empty_set.is_empty());
1542    }
1543
1544    #[rstest]
1545    fn test_hashmap_maplike() {
1546        let mut map: HashMap<String, i32> = HashMap::new();
1547        map.insert("key1".to_string(), 42);
1548        map.insert("key2".to_string(), 100);
1549
1550        assert!(map.contains_key(&"key1".to_string()));
1551        assert!(!map.contains_key(&"missing".to_string()));
1552        assert!(!map.is_empty());
1553
1554        let empty_map: HashMap<String, i32> = HashMap::new();
1555        assert!(empty_map.is_empty());
1556    }
1557
1558    #[rstest]
1559    fn test_indexmap_maplike() {
1560        let mut map: IndexMap<String, i32> = IndexMap::new();
1561        map.insert("key1".to_string(), 42);
1562        map.insert("key2".to_string(), 100);
1563
1564        assert!(map.contains_key(&"key1".to_string()));
1565        assert!(!map.contains_key(&"missing".to_string()));
1566        assert!(!map.is_empty());
1567
1568        let empty_map: IndexMap<String, i32> = IndexMap::new();
1569        assert!(empty_map.is_empty());
1570    }
1571
1572    #[rstest]
1573    fn test_ahashmap_maplike() {
1574        let mut map: AHashMap<String, i32> = AHashMap::new();
1575        map.insert("key1".to_string(), 42);
1576        map.insert("key2".to_string(), 100);
1577
1578        assert!(map.contains_key(&"key1".to_string()));
1579        assert!(!map.contains_key(&"missing".to_string()));
1580        assert!(!map.is_empty());
1581
1582        let empty_map: AHashMap<String, i32> = AHashMap::new();
1583        assert!(empty_map.is_empty());
1584    }
1585
1586    #[rstest]
1587    fn test_trait_object_setlike() {
1588        let mut hashset: HashSet<String> = HashSet::new();
1589        hashset.insert("test".to_string());
1590
1591        let mut indexset: IndexSet<String> = IndexSet::new();
1592        indexset.insert("test".to_string());
1593
1594        let sets: Vec<&dyn SetLike<Item = String>> = vec![&hashset, &indexset];
1595
1596        for set in sets {
1597            assert!(set.contains(&"test".to_string()));
1598            assert!(!set.is_empty());
1599        }
1600    }
1601
1602    #[rstest]
1603    fn test_trait_object_maplike() {
1604        let mut hashmap: HashMap<String, i32> = HashMap::new();
1605        hashmap.insert("key".to_string(), 42);
1606
1607        let mut indexmap: IndexMap<String, i32> = IndexMap::new();
1608        indexmap.insert("key".to_string(), 42);
1609
1610        let maps: Vec<&dyn MapLike<Key = String, Value = i32>> = vec![&hashmap, &indexmap];
1611
1612        for map in maps {
1613            assert!(map.contains_key(&"key".to_string()));
1614            assert!(!map.is_empty());
1615        }
1616    }
1617}