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