1use std::{
2 hash::{BuildHasher, Hash, RandomState},
3 io::Read,
4 iter::repeat_with,
5 sync::{
6 Arc, Mutex, MutexGuard,
7 atomic::{AtomicUsize, Ordering},
8 },
9};
10
11use crate::{
12 associated_data::AssociatedData,
13 bucket::{Bucket, InsertValues, LookupValues},
14 config::CuckooConfiguration,
15 data_block::{DataBlock, Fingerprint},
16 exporter::{
17 CuckooFilterExporter, ExportableBuildHasher, ExportableRandomState, import_config,
18 read_hasher_from,
19 },
20};
21
22#[derive(Clone)]
116pub struct CuckooFilter<H: BuildHasher> {
117 configuration: CuckooConfiguration,
118 buckets: Arc<Vec<Mutex<Bucket>>>,
119 build_hasher: H,
120 items: Arc<AtomicUsize>,
121}
122
123impl CuckooFilter<RandomState> {
124 #[must_use]
130 pub fn new_random(configuration: CuckooConfiguration) -> Self {
131 Self::new(configuration, RandomState::new())
132 }
133}
134
135impl CuckooFilter<ExportableRandomState> {
136 #[must_use]
144 pub fn new_random_exportable(configuration: CuckooConfiguration) -> Self {
145 Self::new(configuration, ExportableRandomState::new_random())
146 }
147
148 pub fn import_random_exportable(reader: impl Read) -> Result<Self, crate::ImportError> {
155 Self::import(reader)
156 }
157}
158
159impl<H: ExportableBuildHasher + BuildHasher> CuckooFilter<H> {
160 pub fn import(mut reader: impl Read) -> Result<Self, crate::ImportError> {
166 let (hasher, configuration) = Self::import_config(&mut reader)?;
167 Self::import_state(hasher, configuration, reader)
168 }
169
170 pub fn import_config(
172 mut reader: impl Read,
173 ) -> Result<(H, CuckooConfiguration), crate::ImportError> {
174 let hasher = read_hasher_from::<H>(&mut reader)?;
175 let config = import_config(&mut reader)?;
176 Ok((hasher, config))
177 }
178
179 pub fn import_state(
186 hasher: H,
187 configuration: CuckooConfiguration,
188 mut reader: impl Read,
189 ) -> Result<Self, crate::ImportError> {
190 let mut buckets = Vec::with_capacity(configuration.bucket_count);
191
192 let mut item_count = 0;
193 for _ in 0..configuration.bucket_count {
194 let bucket = Bucket::take_from(&mut reader, &configuration)?;
195 item_count += bucket.occupied_count(&configuration);
196 buckets.push(Mutex::new(bucket));
197 }
198
199 Ok(Self {
200 configuration,
201 buckets: Arc::new(buckets),
202 build_hasher: hasher,
203 items: Arc::new(AtomicUsize::new(item_count)),
204 })
205 }
206
207 pub fn exporter<'a>(&'a self) -> CuckooFilterExporter<'a, H> {
210 CuckooFilterExporter::new(&self.build_hasher, &self.buckets, &self.configuration)
211 }
212}
213
214impl<H: BuildHasher> CuckooFilter<H> {
215 pub fn new(configuration: CuckooConfiguration, build_hasher: H) -> Self {
221 Self {
222 configuration: configuration.clone(),
223 buckets: repeat_with(|| Bucket::new(&configuration).into())
224 .take(configuration.bucket_count)
225 .collect::<Vec<_>>()
226 .into(),
227 build_hasher,
228 items: Arc::new(AtomicUsize::new(0)),
229 }
230 }
231
232 pub const fn get_bucket_count(&self) -> usize {
238 self.configuration.bucket_count
239 }
240
241 pub fn get_item_count(&self) -> usize {
243 self.items.load(Ordering::Relaxed)
244 }
245
246 pub fn get_configuration(&self) -> CuckooConfiguration {
248 self.configuration.clone()
249 }
250
251 pub fn get_memory_usage(&self) -> usize {
253 size_of::<Self>()
254 + size_of::<AtomicUsize>()
255 + size_of::<Vec<Mutex<Bucket>>>()
256 + size_of::<Mutex<Bucket>>() * self.buckets.len()
257 + self.configuration.bucket_byte_size * self.buckets.len()
258 }
259
260 pub fn insert_if_not_present<K: Hash + ?Sized>(&self, key: &K) -> Option<Fingerprint> {
271 self.insert_if_not_present_with_update(
272 key,
273 InsertValues::default(),
274 LookupValues::default(),
275 )
276 }
277
278 pub fn insert_if_not_present_with_update<K: Hash + ?Sized>(
284 &self,
285 key: &K,
286 insert_values: InsertValues,
287 lookup_update: LookupValues,
288 ) -> Option<Fingerprint> {
289 let (fp, i1) = self.get_fingerprint_and_index(key);
290
291 let mut contains =
292 self.lock_bucket(i1 as usize)
293 .contains(&fp, &self.configuration, &lookup_update);
294
295 if contains {
296 return None;
297 }
298
299 let i2 = self.alt_index(&fp, i1);
300 contains = self
301 .lock_bucket(i2 as usize)
302 .contains(&fp, &self.configuration, &lookup_update);
303
304 if contains {
305 return None;
306 }
307
308 let mut cur_data_block = self.new_data_block(&fp, insert_values);
309
310 let inserted = self
311 .lock_bucket(i1 as usize)
312 .insert(&cur_data_block, &self.configuration);
313
314 if inserted {
315 self.items.fetch_add(1, Ordering::Relaxed);
316 return None;
317 }
318
319 let inserted = self
320 .lock_bucket(i2 as usize)
321 .insert(&cur_data_block, &self.configuration);
322
323 if inserted {
324 self.items.fetch_add(1, Ordering::Relaxed);
325 return None;
326 }
327
328 let mut cur_index = if rand::random::<bool>() { i1 } else { i2 };
329 for _ in 0..self.configuration.max_kicks {
330 {
331 let mut bucket = self.lock_bucket(cur_index as usize);
332 if let Some(lru_config) = self.configuration.lru_field_config.as_ref() {
334 if !bucket.kick_lru(&mut cur_data_block, &self.configuration, lru_config) {
335 return Some(cur_data_block.get_fingerprint(&self.configuration));
336 }
337 } else {
338 bucket.kick_random(&mut cur_data_block, &self.configuration);
339 }
340 cur_index = self.alt_index(
341 &cur_data_block.get_fingerprint(&self.configuration),
342 cur_index,
343 );
344 }
345
346 if self
347 .lock_bucket(cur_index as usize)
348 .insert(&cur_data_block, &self.configuration)
349 {
350 self.items.fetch_add(1, Ordering::Relaxed);
351 return None;
353 }
354 }
355
356 Some(cur_data_block.get_fingerprint(&self.configuration))
358 }
359
360 pub fn insert<K: Hash + ?Sized>(&self, key: &K) -> Option<Fingerprint> {
372 self.insert_with_defaults(key, InsertValues::default())
373 }
374
375 pub fn insert_with_defaults<K: Hash + ?Sized>(
380 &self,
381 key: &K,
382 default: InsertValues,
383 ) -> Option<Fingerprint> {
384 let (fp, i1) = self.get_fingerprint_and_index(key);
385 let mut cur_data_block = self.new_data_block(&fp, default);
386
387 let inserted = self
388 .lock_bucket(i1 as usize)
389 .insert(&cur_data_block, &self.configuration);
390
391 if inserted {
392 self.items.fetch_add(1, Ordering::Relaxed);
393 return None;
394 }
395
396 let i2 = self.alt_index(&fp, i1);
397
398 let inserted = self
399 .lock_bucket(i2 as usize)
400 .insert(&cur_data_block, &self.configuration);
401
402 if inserted {
403 self.items.fetch_add(1, Ordering::Relaxed);
404 return None;
405 }
406
407 let mut cur_index = i1;
408 for _ in 0..self.configuration.max_kicks {
409 {
410 let mut bucket = self.lock_bucket(cur_index as usize);
411 if let Some(lru_config) = self.configuration.lru_field_config.as_ref() {
413 if !bucket.kick_lru(&mut cur_data_block, &self.configuration, lru_config) {
414 return Some(cur_data_block.get_fingerprint(&self.configuration));
415 }
416 } else {
417 bucket.kick_random(&mut cur_data_block, &self.configuration);
419 }
420 cur_index = self.alt_index(
421 &cur_data_block.get_fingerprint(&self.configuration),
422 cur_index,
423 );
424 }
425
426 if self
427 .lock_bucket(cur_index as usize)
428 .insert(&cur_data_block, &self.configuration)
429 {
430 self.items.fetch_add(1, Ordering::Relaxed);
431 return None;
433 }
434 }
435
436 Some(cur_data_block.get_fingerprint(&self.configuration))
438 }
439
440 pub fn contains_with_update<K: Hash + ?Sized>(&self, key: &K, update: LookupValues) -> bool {
445 let (fp, i1) = self.get_fingerprint_and_index(key);
446
447 let mut contains =
448 self.lock_bucket(i1 as usize)
449 .contains(&fp, &self.configuration, &update);
450
451 if !contains {
452 let i2 = self.alt_index(&fp, i1);
453 contains = self
454 .lock_bucket(i2 as usize)
455 .contains(&fp, &self.configuration, &update);
456 }
457
458 contains
459 }
460
461 pub fn contains<K: Hash + ?Sized>(&self, key: &K) -> bool {
466 self.contains_with_update(key, LookupValues::default())
467 }
468
469 pub fn get_associated_data<K: Hash + ?Sized>(&self, key: &K) -> Option<AssociatedData> {
476 self.get_associated_data_with_update(key, LookupValues::default())
477 }
478
479 pub fn get_associated_data_with_update<K: Hash + ?Sized>(
484 &self,
485 key: &K,
486 update: LookupValues,
487 ) -> Option<AssociatedData> {
488 let (fp, i1) = self.get_fingerprint_and_index(key);
489
490 let mut contains =
491 self.lock_bucket(i1 as usize)
492 .get_associated_data(&fp, &self.configuration, &update);
493
494 if contains.is_none() {
495 let i2 = self.alt_index(&fp, i1);
496 contains = self.lock_bucket(i2 as usize).get_associated_data(
497 &fp,
498 &self.configuration,
499 &update,
500 );
501 }
502
503 contains
504 }
505
506 pub fn remove<K: Hash + ?Sized>(&self, key: &K) -> bool {
510 let (fp, i1) = self.get_fingerprint_and_index(key);
511
512 let mut removed = self
513 .lock_bucket(i1 as usize)
514 .remove(&fp, &self.configuration);
515
516 if !removed {
517 let i2 = self.alt_index(&fp, i1);
518 removed = self
519 .lock_bucket(i2 as usize)
520 .remove(&fp, &self.configuration);
521 }
522
523 if removed {
524 self.items.fetch_sub(1, Ordering::Relaxed);
525 }
526
527 removed
528 }
529
530 pub fn scan_and_update_full(&self) -> usize {
573 if self.configuration.lru_field_config.is_none()
574 && self.configuration.ttl_field_config.is_none()
575 {
576 return 0;
577 }
578
579 let mut removed = 0;
580 for b in self.buckets.iter() {
581 #[expect(clippy::unwrap_used)]
582 let mut bucket = b.lock().unwrap();
583 if let Some(lru_config) = &self.configuration.lru_field_config {
584 bucket.age_lru_counters(&self.configuration, lru_config);
585 }
586 if let Some(ttl_config) = &self.configuration.ttl_field_config {
587 removed += bucket.age_ttl_counters(&self.configuration, ttl_config);
588 }
589 }
590
591 if removed > 0 {
592 self.items.fetch_sub(removed, Ordering::Relaxed);
593 }
594 removed
595 }
596
597 pub fn scan_and_update_ttl(&self) -> usize {
604 if self.configuration.ttl_field_config.is_none() {
605 return 0;
606 }
607
608 let mut removed = 0;
609 for b in self.buckets.iter() {
610 #[expect(clippy::unwrap_used)]
611 let mut bucket = b.lock().unwrap();
612 if let Some(ttl_config) = &self.configuration.ttl_field_config {
613 removed += bucket.age_ttl_counters(&self.configuration, ttl_config);
614 }
615 }
616
617 if removed > 0 {
618 self.items.fetch_sub(removed, Ordering::Relaxed);
619 }
620 removed
621 }
622
623 pub fn scan_and_update_lru(&self) {
630 if self.configuration.lru_field_config.is_none() {
631 return;
632 }
633
634 for b in self.buckets.iter() {
635 #[expect(clippy::unwrap_used)]
636 let mut bucket = b.lock().unwrap();
637 if let Some(lru_config) = &self.configuration.lru_field_config {
638 bucket.age_lru_counters(&self.configuration, lru_config);
639 }
640 }
641 }
642
643 pub(crate) fn get_fingerprint<K: Hash + ?Sized>(&self, key: &K) -> Fingerprint {
645 self.get_fingerprint_and_index(key).0
646 }
647
648 fn new_data_block(&self, fp: &Fingerprint, defaults: InsertValues) -> DataBlock<Vec<u8>> {
649 let data = vec![0u8; self.configuration.data_block_size];
650 let mut cur_data_block = DataBlock::from(data);
651 cur_data_block.store_fingerprint(fp, &self.configuration);
652
653 if let Some(ttl_config) = self.configuration.ttl_field_config.as_ref() {
654 cur_data_block.set_ttl(ttl_config, defaults.ttl.unwrap_or(ttl_config.0.ttl.into()));
655 }
656 if let Some(counter_config) = self.configuration.counter_field_config.as_ref() {
657 cur_data_block.update_counter(
658 counter_config,
659 defaults
660 .counter
661 .unwrap_or(counter_config.0.change_on_insert),
662 );
663 }
664 if let Some(lru_config) = self.configuration.lru_field_config.as_ref() {
665 cur_data_block.inc_lru_counter(lru_config);
666 }
667 cur_data_block
668 }
669
670 fn get_fingerprint_and_index<K: Hash + ?Sized>(&self, key: &K) -> (Fingerprint, u32) {
671 let result = self.build_hasher.hash_one(key);
672
673 let fingerprint = (result >> 32) as u32;
676 #[expect(clippy::cast_possible_truncation)]
678 let index = result as u32 & self.configuration.buckets_mask;
679
680 (
681 Fingerprint::new(
682 fingerprint,
683 self.configuration.fingerprint_field_config.value_mask(),
684 ),
685 index,
686 )
687 }
688
689 #[expect(clippy::cast_possible_truncation)]
691 fn alt_index(&self, fingerprint: &Fingerprint, index: u32) -> u32 {
692 let result = self.build_hasher.hash_one(fingerprint);
693
694 (index ^ ((result as u32) & self.configuration.buckets_mask))
695 & self.configuration.buckets_mask
696 }
697
698 #[expect(clippy::unwrap_used)]
699 fn lock_bucket(&self, index: usize) -> MutexGuard<'_, Bucket> {
700 self.buckets[index].lock().unwrap()
703 }
704}
705
706#[cfg(test)]
707#[expect(clippy::unwrap_used)]
708mod tests {
709 use std::{
710 collections::{HashSet, VecDeque},
711 hash::Hasher,
712 ops::Range,
713 };
714
715 use crate::config::{CounterConfig, LruConfig, TtlConfig};
716
717 use super::*;
718
719 fn get_words(range: Range<usize>) -> Vec<String> {
720 std::fs::read_to_string("/usr/share/dict/words")
721 .unwrap()
722 .split("\n")
723 .skip(range.start)
724 .take(range.len())
725 .map(ToString::to_string)
726 .collect()
727 }
728
729 #[test]
730 fn basic_insertion() {
731 let filter = CuckooFilter::new_random(CuckooConfiguration::builder(1000).build().unwrap());
732
733 filter.insert("basic");
734
735 assert!(filter.contains("basic"));
736 }
737
738 #[test]
739 fn basic_removal() {
740 let filter = CuckooFilter::new_random(CuckooConfiguration::builder(1000).build().unwrap());
741
742 filter.insert("basic");
743
744 assert!(filter.contains("basic"));
745
746 filter.remove("basic");
747
748 assert!(!filter.contains("basic"));
749 }
750
751 struct PredefinedBucketItem(u64);
752 struct TestHasher(u64);
753 impl BuildHasher for TestHasher {
754 type Hasher = TestHasher;
755
756 fn build_hasher(&self) -> Self::Hasher {
757 TestHasher(0)
758 }
759 }
760 impl Hasher for TestHasher {
761 fn finish(&self) -> u64 {
762 self.0
763 }
764
765 fn write(&mut self, bytes: &[u8]) {
766 if bytes.len() == 8 {
767 self.0 = u64::from_ne_bytes(bytes.try_into().unwrap());
768 } else {
769 self.0 = 1 - (u32::from_ne_bytes(bytes.try_into().unwrap()) as u64 % 2);
771 }
772 }
773 }
774 impl Hash for PredefinedBucketItem {
775 fn hash<H: Hasher>(&self, state: &mut H) {
776 state.write_u64(self.0);
777 }
778 }
779
780 #[test]
781 fn lru_insertion() {
782 let filter = CuckooFilter::new(
783 CuckooConfiguration::builder(1000)
784 .bucket_size(2.try_into().unwrap())
785 .with_lru(LruConfig {
786 counter_bits: 8.try_into().unwrap(),
787 })
788 .build()
789 .unwrap(),
790 TestHasher(0),
791 );
792
793 let test_item = PredefinedBucketItem(2 << 32);
794 filter.insert(&test_item);
795 filter.contains(&test_item); let test_item_2 = PredefinedBucketItem(4 << 32);
798 filter.insert(&test_item_2); let test_item_3 = PredefinedBucketItem((3 << 32) + 2);
801 filter.insert(&test_item_3); filter.contains(&test_item_3); let test_item_4 = PredefinedBucketItem((5 << 32) + 2);
805 filter.insert(&test_item_4); assert!(filter.contains(&test_item));
809 assert!(filter.contains(&test_item_2));
810 assert!(filter.contains(&test_item_3));
811 assert!(filter.contains(&test_item_4));
812
813 let test_item_5 = PredefinedBucketItem((1 << 32) + 2);
814 filter.insert(&test_item_5);
816
817 assert!(filter.contains(&test_item_2));
818 assert!(filter.contains(&test_item));
819 assert!(filter.contains(&test_item_3));
820
821 assert!(
822 !filter.contains(&test_item_5) || !filter.contains(&test_item_4),
823 "No inserted items are missing, but filter can't hold them all"
824 );
825
826 filter.insert(&test_item_5);
828 filter.insert(&test_item_4);
829 assert!(filter.contains(&test_item));
830 assert!(filter.contains(&test_item_3));
831 }
832
833 #[test]
834 fn alt_index() {
835 let words = get_words(0..200_000);
836 let filter = CuckooFilter::new_random(
837 CuckooConfiguration::builder(200_000)
838 .fingerprint_bits(32.try_into().unwrap())
839 .build()
840 .unwrap(),
841 );
842
843 for word in words {
844 let (fp, index) = filter.get_fingerprint_and_index(&word);
845 let alt_index = filter.alt_index(&fp, index);
846 assert_eq!(index, filter.alt_index(&fp, alt_index));
847 }
848 }
849
850 #[test]
851 fn random_kicks() {
852 let filter = CuckooFilter::new(
853 CuckooConfiguration::builder(1000)
854 .bucket_size(2.try_into().unwrap())
855 .build()
856 .unwrap(),
857 TestHasher(0),
858 );
859
860 let test_item = PredefinedBucketItem(2 << 32);
861 filter.insert(&test_item);
862
863 let test_item_2 = PredefinedBucketItem(4 << 32);
864 filter.insert(&test_item_2); let test_item_3 = PredefinedBucketItem((3 << 32) + 2);
867 filter.insert(&test_item_3); let test_item_4 = PredefinedBucketItem((5 << 32) + 2);
870 filter.insert(&test_item_4); let test_item_unrelated = PredefinedBucketItem((10 << 32) + 10);
874 filter.insert(&test_item_unrelated);
875
876 assert!(filter.contains(&test_item));
878 assert!(filter.contains(&test_item_2));
879 assert!(filter.contains(&test_item_3));
880 assert!(filter.contains(&test_item_4));
881
882 let test_item_5 = PredefinedBucketItem((1 << 32) + 2);
883 let kicked = filter.insert(&test_item_5);
885 assert!(kicked.is_some(), "An item had to be kicked");
886 assert!(filter.contains(&test_item_5));
887 assert!(filter.contains(&test_item_unrelated));
888
889 for item in [&test_item, &test_item_2, &test_item_3, &test_item_4]
890 .iter()
891 .filter(|i| !kicked.as_ref().unwrap().matches_key(i, &filter))
892 {
893 assert!(filter.contains(item), "Only one item should be kicked");
894 }
895 }
896
897 #[test]
898 fn overriding_defaults() {
899 let filter = CuckooFilter::new_random(
900 CuckooConfiguration::builder(1000)
901 .with_ttl(TtlConfig {
902 ttl: 30.try_into().unwrap(),
903 ttl_bits: 8.try_into().unwrap(),
904 })
905 .with_counter(CounterConfig::default())
906 .build()
907 .unwrap(),
908 );
909
910 filter.insert_with_defaults(
911 "basic",
912 InsertValues {
913 ttl: Some(50),
914 counter: Some(10),
915 },
916 );
917
918 assert!(filter.contains("basic"));
919 assert_eq!(
920 filter
921 .get_associated_data("basic")
922 .unwrap()
923 .get_stored_ttl_value()
924 .unwrap(),
925 50
926 );
927 assert_eq!(
928 filter
929 .get_associated_data("basic")
930 .unwrap()
931 .get_counter()
932 .unwrap(),
933 13 );
935 }
936
937 #[test]
938 fn overriding_updates() {
939 let filter = CuckooFilter::new_random(
940 CuckooConfiguration::builder(1000)
941 .with_ttl(TtlConfig {
942 ttl: 30.try_into().unwrap(),
943 ttl_bits: 8.try_into().unwrap(),
944 })
945 .with_counter(CounterConfig::default())
946 .build()
947 .unwrap(),
948 );
949
950 filter.insert_with_defaults(
951 "basic",
952 InsertValues {
953 ttl: Some(5),
954 counter: Some(1),
955 },
956 );
957
958 assert!(filter.contains_with_update(
959 "basic",
960 LookupValues {
961 ttl: Some(50),
962 counter_diff: Some(10),
963 },
964 ));
965 assert_eq!(
966 filter
967 .get_associated_data("basic")
968 .unwrap()
969 .get_stored_ttl_value()
970 .unwrap(),
971 50
972 );
973 assert_eq!(
974 filter
975 .get_associated_data("basic")
976 .unwrap()
977 .get_counter()
978 .unwrap(),
979 13 );
981 }
982
983 #[test]
984 fn scan_and_update_full() {
985 let words = get_words(0..100_000);
986 let filter = CuckooFilter::new_random(
987 CuckooConfiguration::builder(100_000)
988 .fingerprint_bits(32.try_into().unwrap())
989 .with_lru(LruConfig::default())
990 .with_ttl(TtlConfig {
991 ttl: 3.try_into().unwrap(),
992 ttl_bits: 2.try_into().unwrap(),
993 })
994 .build()
995 .unwrap(),
996 );
997
998 assert_eq!(filter.get_item_count(), 0);
999
1000 let mut stored_words = HashSet::new();
1001
1002 for (index, word) in words.iter().enumerate() {
1003 stored_words.insert(word);
1004 if let Some(evicted_fp) = filter.insert(word) {
1005 words[0..=index]
1006 .iter()
1007 .filter(|w| evicted_fp.matches_key(w, &filter))
1008 .for_each(|evicted_word| {
1009 stored_words.remove(evicted_word);
1010 });
1011 }
1012 }
1013
1014 assert_eq!(filter.get_item_count(), stored_words.len());
1015
1016 for _ in 0..2 {
1017 assert_eq!(filter.scan_and_update_full(), 0);
1018 }
1019
1020 assert_eq!(filter.get_item_count(), stored_words.len());
1021 for word in stored_words.iter() {
1022 assert!(
1023 filter.contains(word),
1024 "Word: {word} expected in the filter, but not found"
1025 );
1026 }
1027
1028 assert_eq!(filter.scan_and_update_full(), stored_words.len());
1030 for word in &words {
1031 assert!(
1032 !filter.contains(word),
1033 "Filter contained {word}, but shouldn't have"
1034 );
1035 }
1036 assert_eq!(filter.get_item_count(), 0);
1037 }
1038
1039 #[test]
1040 fn export_import() {
1041 let words = get_words(0..100_000);
1042 let filter = CuckooFilter::new_random_exportable(
1043 CuckooConfiguration::builder(100_000)
1044 .fingerprint_bits(32.try_into().unwrap())
1045 .with_lru(LruConfig::default())
1046 .with_ttl(TtlConfig {
1047 ttl: 3.try_into().unwrap(),
1048 ttl_bits: 2.try_into().unwrap(),
1049 })
1050 .build()
1051 .unwrap(),
1052 );
1053
1054 assert_eq!(filter.get_item_count(), 0);
1055
1056 let mut stored_words = HashSet::new();
1057
1058 for (index, word) in words.iter().enumerate() {
1059 stored_words.insert(word);
1060 if let Some(evicted_fp) = filter.insert(word) {
1061 words[0..=index]
1062 .iter()
1063 .filter(|w| evicted_fp.matches_key(w, &filter))
1064 .for_each(|evicted_word| {
1065 stored_words.remove(evicted_word);
1066 });
1067 }
1068 }
1069
1070 assert_eq!(filter.get_item_count(), stored_words.len());
1071
1072 let exported_buf = filter.exporter().snapshot().unwrap();
1073 let mut readable_buf = VecDeque::from(exported_buf);
1074
1075 let imported_filter = CuckooFilter::import_random_exportable(&mut readable_buf).unwrap();
1076
1077 assert_eq!(
1078 imported_filter.get_configuration(),
1079 filter.get_configuration()
1080 );
1081
1082 assert_eq!(imported_filter.get_item_count(), stored_words.len());
1083 for word in stored_words.iter() {
1084 assert!(
1085 imported_filter.contains(word),
1086 "Word: {word} expected in the filter, but not found"
1087 );
1088 }
1089 }
1090}