1use super::{deques::Deques, CacheBuilder, Iter, KeyHashDate, ValueEntry};
2use crate::{
3 common::{self, deque::DeqNode, frequency_sketch::FrequencySketch, CacheRegion},
4 Policy,
5};
6
7use smallvec::SmallVec;
8use std::{
9 borrow::Borrow,
10 collections::{hash_map::RandomState, HashMap},
11 fmt,
12 hash::{BuildHasher, Hash},
13 ptr::NonNull,
14 rc::Rc,
15};
16
17const EVICTION_BATCH_SIZE: usize = 100;
18
19type CacheStore<K, V, S> = std::collections::HashMap<Rc<K>, ValueEntry<K, V>, S>;
20
21pub struct Cache<K, V, S = RandomState> {
95 max_capacity: Option<u64>,
96 entry_count: u64,
97 cache: CacheStore<K, V, S>,
98 build_hasher: S,
99 deques: Deques<K>,
100 frequency_sketch: FrequencySketch,
101 frequency_sketch_enabled: bool,
102}
103
104impl<K, V, S> fmt::Debug for Cache<K, V, S>
105where
106 K: fmt::Debug + Eq + Hash,
107 V: fmt::Debug,
108 S: BuildHasher + Clone,
110{
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 let mut d_map = f.debug_map();
113
114 for (k, v) in self.iter() {
115 d_map.entry(&k, &v);
116 }
117
118 d_map.finish()
119 }
120}
121
122impl<K, V> Cache<K, V, RandomState>
123where
124 K: Hash + Eq,
125{
126 pub fn new(max_capacity: u64) -> Self {
133 let build_hasher = RandomState::default();
134 Self::with_everything(Some(max_capacity), None, build_hasher)
135 }
136
137 pub fn builder() -> CacheBuilder<K, V, Cache<K, V, RandomState>> {
142 CacheBuilder::default()
143 }
144}
145
146impl<K, V, S> Cache<K, V, S> {
150 pub fn policy(&self) -> Policy {
155 Policy::new(self.max_capacity)
156 }
157
158 pub fn entry_count(&self) -> u64 {
178 self.entry_count
179 }
180
181 pub fn weighted_size(&self) -> u64 {
185 self.entry_count
186 }
187}
188
189impl<K, V, S> Cache<K, V, S>
190where
191 K: Hash + Eq,
192 S: BuildHasher + Clone,
193{
194 pub(crate) fn with_everything(
195 max_capacity: Option<u64>,
196 initial_capacity: Option<usize>,
197 build_hasher: S,
198 ) -> Self {
199 let cache = HashMap::with_capacity_and_hasher(
200 initial_capacity.unwrap_or_default(),
201 build_hasher.clone(),
202 );
203
204 Self {
205 max_capacity,
206 entry_count: 0,
207 cache,
208 build_hasher,
209 deques: Default::default(),
210 frequency_sketch: Default::default(),
211 frequency_sketch_enabled: false,
212 }
213 }
214
215 pub fn contains_key<Q>(&mut self, key: &Q) -> bool
223 where
224 Rc<K>: Borrow<Q>,
225 Q: Hash + Eq + ?Sized,
226 {
227 self.evict_lru_entries();
228 self.cache.contains_key(key)
229 }
230
231 pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
236 where
237 Rc<K>: Borrow<Q>,
238 Q: Hash + Eq + ?Sized,
239 {
240 self.evict_lru_entries();
241 self.frequency_sketch.increment(self.hash(key));
242
243 if let Some(entry) = self.cache.get_mut(key) {
244 Self::record_hit(&mut self.deques, entry);
245 Some(&entry.value)
246 } else {
247 None
248 }
249 }
250
251 pub fn insert(&mut self, key: K, value: V) {
255 self.evict_lru_entries();
256 let policy_weight = 1;
257 let key = Rc::new(key);
258 let entry = ValueEntry::new(value);
259
260 if let Some(old_entry) = self.cache.insert(Rc::clone(&key), entry) {
261 self.handle_update(key, policy_weight, old_entry);
262 } else {
263 let hash = self.hash(&key);
264 self.handle_insert(key, hash, policy_weight);
265 }
266 }
267
268 pub fn invalidate<Q>(&mut self, key: &Q)
273 where
274 Rc<K>: Borrow<Q>,
275 Q: Hash + Eq + ?Sized,
276 {
277 self.evict_lru_entries();
278
279 if let Some(mut entry) = self.cache.remove(key) {
280 self.deques.unlink_ao(&mut entry);
281 self.entry_count -= 1;
282 }
283 }
284
285 pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
290 where
291 Rc<K>: Borrow<Q>,
292 Q: Hash + Eq + ?Sized,
293 {
294 self.evict_lru_entries();
295
296 if let Some(mut entry) = self.cache.remove(key) {
297 self.deques.unlink_ao(&mut entry);
298 self.entry_count -= 1;
299 Some(entry.value)
300 } else {
301 None
302 }
303 }
304
305 pub fn invalidate_all(&mut self) {
311 let old_capacity = self.cache.capacity();
314 let old_cache = std::mem::replace(
315 &mut self.cache,
316 HashMap::with_hasher(self.build_hasher.clone()),
317 );
318 self.deques.clear();
319 self.entry_count = 0;
320
321 drop(old_cache);
323
324 let _ = self.cache.try_reserve(old_capacity);
326 }
327
328 #[allow(clippy::needless_collect)]
343 pub fn invalidate_entries_if(&mut self, mut predicate: impl FnMut(&K, &V) -> bool) {
344 let Self { cache, deques, .. } = self;
345
346 let keys_to_invalidate = cache
352 .iter()
353 .filter(|(key, entry)| (predicate)(key, &entry.value))
354 .map(|(key, _)| Rc::clone(key))
355 .collect::<Vec<_>>();
356
357 let mut invalidated = 0u64;
358
359 keys_to_invalidate.into_iter().for_each(|k| {
360 if let Some(mut entry) = cache.remove(&k) {
361 let _weight = entry.policy_weight();
362 deques.unlink_ao(&mut entry);
363 invalidated += 1;
364 }
365 });
366 self.entry_count -= invalidated;
367 }
368
369 pub fn iter(&self) -> Iter<'_, K, V> {
392 Iter::new(self, self.cache.iter())
393 }
394}
395
396impl<K, V, S> Cache<K, V, S>
400where
401 K: Hash + Eq,
402 S: BuildHasher + Clone,
403{
404 #[inline]
405 fn hash<Q>(&self, key: &Q) -> u64
406 where
407 Rc<K>: Borrow<Q>,
408 Q: Hash + Eq + ?Sized,
409 {
410 self.build_hasher.hash_one(key)
411 }
412
413 fn record_hit(deques: &mut Deques<K>, entry: &mut ValueEntry<K, V>) {
414 deques.move_to_back_ao(entry)
415 }
416
417 fn has_enough_capacity(&self, candidate_weight: u32, ws: u64) -> bool {
418 self.max_capacity
419 .map(|limit| ws + candidate_weight as u64 <= limit)
420 .unwrap_or(true)
421 }
422
423 fn weights_to_evict(&self) -> u64 {
424 self.max_capacity
425 .map(|limit| self.entry_count.saturating_sub(limit))
426 .unwrap_or_default()
427 }
428
429 #[inline]
430 fn should_enable_frequency_sketch(&self) -> bool {
431 if self.frequency_sketch_enabled {
432 false
433 } else if let Some(max_cap) = self.max_capacity {
434 self.entry_count >= max_cap / 2
435 } else {
436 false
437 }
438 }
439
440 #[inline]
441 fn enable_frequency_sketch(&mut self) {
442 if let Some(max_cap) = self.max_capacity {
443 self.do_enable_frequency_sketch(max_cap);
444 }
445 }
446
447 #[cfg(test)]
448 fn enable_frequency_sketch_for_testing(&mut self) {
449 if let Some(max_cap) = self.max_capacity {
450 self.do_enable_frequency_sketch(max_cap);
451 }
452 }
453
454 #[inline]
455 fn do_enable_frequency_sketch(&mut self, cache_capacity: u64) {
456 let skt_capacity = common::sketch_capacity(cache_capacity);
457 self.frequency_sketch.ensure_capacity(skt_capacity);
458 self.frequency_sketch_enabled = true;
459 }
460
461 #[inline]
462 fn handle_insert(&mut self, key: Rc<K>, hash: u64, policy_weight: u32) {
463 let has_free_space = self.has_enough_capacity(policy_weight, self.entry_count);
464 let (cache, deqs, freq) = (&mut self.cache, &mut self.deques, &self.frequency_sketch);
465
466 if has_free_space {
467 let key = Rc::clone(&key);
469 let entry = cache.get_mut(&key).unwrap();
470 deqs.push_back_ao(
471 CacheRegion::MainProbation,
472 KeyHashDate::new(Rc::clone(&key), hash),
473 entry,
474 );
475 self.entry_count += 1;
476 if self.should_enable_frequency_sketch() {
479 self.enable_frequency_sketch();
480 }
481
482 return;
483 }
484
485 if let Some(max) = self.max_capacity {
486 if policy_weight as u64 > max {
487 cache.remove(&Rc::clone(&key));
489 return;
490 }
491 }
492
493 let mut candidate = EntrySizeAndFrequency::new(policy_weight as u64);
494 candidate.add_frequency(freq, hash);
495
496 match Self::admit(&candidate, cache, deqs, freq) {
497 AdmissionResult::Admitted { victim_nodes } => {
498 for victim in victim_nodes {
500 let mut vic_entry = cache
502 .remove(unsafe { &victim.as_ref().element.key })
503 .expect("Cannot remove a victim from the hash map");
504 deqs.unlink_ao(&mut vic_entry);
506 self.entry_count -= 1;
508 }
509
510 let entry = cache.get_mut(&key).unwrap();
512 let key = Rc::clone(&key);
513 deqs.push_back_ao(
514 CacheRegion::MainProbation,
515 KeyHashDate::new(Rc::clone(&key), hash),
516 entry,
517 );
518
519 self.entry_count += 1;
520 if self.should_enable_frequency_sketch() {
524 self.enable_frequency_sketch();
525 }
526 }
527 AdmissionResult::Rejected => {
528 cache.remove(&key);
530 }
531 }
532 }
533
534 #[inline]
552 fn admit(
553 candidate: &EntrySizeAndFrequency,
554 _cache: &CacheStore<K, V, S>,
555 deqs: &Deques<K>,
556 freq: &FrequencySketch,
557 ) -> AdmissionResult<K> {
558 let mut victims = EntrySizeAndFrequency::default();
559 let mut victim_nodes = SmallVec::default();
560
561 let mut next_victim = deqs.probation.peek_front_ptr();
563
564 while victims.weight < candidate.weight {
566 if candidate.freq < victims.freq {
567 break;
568 }
569 if let Some(victim) = next_victim.take() {
570 next_victim = DeqNode::next_node_ptr(victim);
571 let vic_elem = &unsafe { victim.as_ref() }.element;
572
573 victims.add_policy_weight();
577 victims.add_frequency(freq, vic_elem.hash);
578 victim_nodes.push(victim);
579 } else {
580 break;
582 }
583 }
584
585 if victims.weight >= candidate.weight && candidate.freq > victims.freq {
591 AdmissionResult::Admitted { victim_nodes }
592 } else {
593 AdmissionResult::Rejected
594 }
595 }
596
597 fn handle_update(&mut self, key: Rc<K>, policy_weight: u32, old_entry: ValueEntry<K, V>) {
598 let entry = self.cache.get_mut(&key).unwrap();
599 entry.replace_deq_nodes_with(old_entry);
600 entry.set_policy_weight(policy_weight);
601
602 let deqs = &mut self.deques;
603 deqs.move_to_back_ao(entry);
604
605 }
608
609 #[inline]
610 fn evict_lru_entries(&mut self) {
611 const DEQ_NAME: &str = "probation";
612
613 let weights_to_evict = self.weights_to_evict();
614 let mut evicted_count = 0u64;
615 let mut evicted_policy_weight = 0u64;
616
617 {
618 let deqs = &mut self.deques;
619 let (probation, cache) = (&mut deqs.probation, &mut self.cache);
620
621 for _ in 0..EVICTION_BATCH_SIZE {
622 if evicted_policy_weight >= weights_to_evict {
623 break;
624 }
625
626 #[allow(clippy::map_clone)]
629 let key = probation
630 .peek_front()
631 .map(|node| Rc::clone(&node.element.key));
632
633 if key.is_none() {
634 break;
635 }
636 let key = key.unwrap();
637
638 if let Some(mut entry) = cache.remove(&key) {
639 let weight = entry.policy_weight();
640 Deques::unlink_ao_from_deque(DEQ_NAME, probation, &mut entry);
641 evicted_count += 1;
642 evicted_policy_weight = evicted_policy_weight.saturating_add(weight as u64);
643 } else {
644 probation.pop_front();
645 }
646 }
647 }
648
649 self.entry_count -= evicted_count;
650 }
652}
653
654#[cfg(test)]
658impl<K, V, S> Cache<K, V, S>
659where
660 K: Hash + Eq,
661 S: BuildHasher + Clone,
662{
663}
664
665#[derive(Default)]
666struct EntrySizeAndFrequency {
667 weight: u64,
668 freq: u32,
669}
670
671impl EntrySizeAndFrequency {
672 fn new(policy_weight: u64) -> Self {
673 Self {
674 weight: policy_weight,
675 ..Default::default()
676 }
677 }
678
679 fn add_policy_weight(&mut self) {
680 self.weight += 1;
681 }
682
683 fn add_frequency(&mut self, freq: &FrequencySketch, hash: u64) {
684 self.freq += freq.frequency(hash) as u32;
685 }
686}
687
688type AoqNode<K> = NonNull<DeqNode<KeyHashDate<K>>>;
690
691enum AdmissionResult<K> {
692 Admitted {
693 victim_nodes: SmallVec<[AoqNode<K>; 8]>,
694 },
695 Rejected,
696}
697
698#[cfg(test)]
704mod tests {
705 use super::Cache;
706
707 #[test]
708 fn basic_single_thread() {
709 let mut cache = Cache::new(3);
710 cache.enable_frequency_sketch_for_testing();
711
712 cache.insert("a", "alice");
713 cache.insert("b", "bob");
714 assert_eq!(cache.get(&"a"), Some(&"alice"));
715 assert!(cache.contains_key(&"a"));
716 assert!(cache.contains_key(&"b"));
717 assert_eq!(cache.get(&"b"), Some(&"bob"));
718 cache.insert("c", "cindy");
721 assert_eq!(cache.get(&"c"), Some(&"cindy"));
722 assert!(cache.contains_key(&"c"));
723 assert!(cache.contains_key(&"a"));
726 assert_eq!(cache.get(&"a"), Some(&"alice"));
727 assert_eq!(cache.get(&"b"), Some(&"bob"));
728 assert!(cache.contains_key(&"b"));
729 cache.insert("d", "david"); assert_eq!(cache.get(&"d"), None); assert!(!cache.contains_key(&"d"));
735
736 cache.insert("d", "david");
737 assert!(!cache.contains_key(&"d"));
738 assert_eq!(cache.get(&"d"), None); cache.insert("d", "dennis");
743 assert_eq!(cache.get(&"a"), Some(&"alice"));
744 assert_eq!(cache.get(&"b"), Some(&"bob"));
745 assert_eq!(cache.get(&"c"), None);
746 assert_eq!(cache.get(&"d"), Some(&"dennis"));
747 assert!(cache.contains_key(&"a"));
748 assert!(cache.contains_key(&"b"));
749 assert!(!cache.contains_key(&"c"));
750 assert!(cache.contains_key(&"d"));
751
752 cache.invalidate(&"b");
753 assert_eq!(cache.get(&"b"), None);
754 assert!(!cache.contains_key(&"b"));
755 }
756
757 #[test]
758 fn invalidate_all() {
759 let mut cache = Cache::new(100);
760 cache.enable_frequency_sketch_for_testing();
761
762 cache.insert("a", "alice");
763 cache.insert("b", "bob");
764 cache.insert("c", "cindy");
765 assert_eq!(cache.get(&"a"), Some(&"alice"));
766 assert_eq!(cache.get(&"b"), Some(&"bob"));
767 assert_eq!(cache.get(&"c"), Some(&"cindy"));
768 assert!(cache.contains_key(&"a"));
769 assert!(cache.contains_key(&"b"));
770 assert!(cache.contains_key(&"c"));
771
772 cache.invalidate_all();
773
774 cache.insert("d", "david");
775
776 assert!(cache.get(&"a").is_none());
777 assert!(cache.get(&"b").is_none());
778 assert!(cache.get(&"c").is_none());
779 assert_eq!(cache.get(&"d"), Some(&"david"));
780 assert!(!cache.contains_key(&"a"));
781 assert!(!cache.contains_key(&"b"));
782 assert!(!cache.contains_key(&"c"));
783 assert!(cache.contains_key(&"d"));
784 }
785
786 #[test]
787 fn invalidate_entries_if() {
788 use std::collections::HashSet;
789
790 let mut cache = Cache::new(100);
791 cache.enable_frequency_sketch_for_testing();
792
793 cache.insert(0, "alice");
794 cache.insert(1, "bob");
795 cache.insert(2, "alex");
796
797 assert_eq!(cache.get(&0), Some(&"alice"));
798 assert_eq!(cache.get(&1), Some(&"bob"));
799 assert_eq!(cache.get(&2), Some(&"alex"));
800 assert!(cache.contains_key(&0));
801 assert!(cache.contains_key(&1));
802 assert!(cache.contains_key(&2));
803
804 let names = ["alice", "alex"].iter().cloned().collect::<HashSet<_>>();
805 cache.invalidate_entries_if(move |_k, &v| names.contains(v));
806
807 cache.insert(3, "alice");
808
809 assert!(cache.get(&0).is_none());
810 assert!(cache.get(&2).is_none());
811 assert_eq!(cache.get(&1), Some(&"bob"));
812 assert_eq!(cache.get(&3), Some(&"alice"));
814
815 assert!(!cache.contains_key(&0));
816 assert!(cache.contains_key(&1));
817 assert!(!cache.contains_key(&2));
818 assert!(cache.contains_key(&3));
819
820 assert_eq!(cache.cache.len(), 2);
821
822 cache.invalidate_entries_if(|_k, &v| v == "alice");
823 cache.invalidate_entries_if(|_k, &v| v == "bob");
824
825 assert!(cache.get(&1).is_none());
826 assert!(cache.get(&3).is_none());
827
828 assert!(!cache.contains_key(&1));
829 assert!(!cache.contains_key(&3));
830
831 assert_eq!(cache.cache.len(), 0);
832 }
833
834 #[cfg_attr(target_pointer_width = "16", ignore)]
835 #[test]
836 fn test_skt_capacity_will_not_overflow() {
837 let pot = |exp| 2u64.pow(exp);
839
840 let ensure_sketch_len = |max_capacity, len, name| {
841 let mut cache = Cache::<u8, u8>::new(max_capacity);
842 cache.enable_frequency_sketch_for_testing();
843 assert_eq!(cache.frequency_sketch.table_len(), len as usize, "{}", name);
844 };
845
846 if cfg!(target_pointer_width = "32") {
847 let pot24 = pot(24);
848 let pot16 = pot(16);
849 ensure_sketch_len(0, 128, "0");
850 ensure_sketch_len(128, 128, "128");
851 ensure_sketch_len(pot16, pot16, "pot16");
852 ensure_sketch_len(pot16 + 1, pot(17), "pot16 + 1");
854 ensure_sketch_len(pot24 - 1, pot24, "pot24 - 1");
856 ensure_sketch_len(pot24, pot24, "pot24");
857 ensure_sketch_len(pot(27), pot24, "pot(27)");
858 ensure_sketch_len(u32::MAX as u64, pot24, "u32::MAX");
859 } else {
860 let pot30 = pot(30);
862 let pot16 = pot(16);
863 ensure_sketch_len(0, 128, "0");
864 ensure_sketch_len(128, 128, "128");
865 ensure_sketch_len(pot16, pot16, "pot16");
866 ensure_sketch_len(pot16 + 1, pot(17), "pot16 + 1");
868
869 if !cfg!(circleci) {
872 ensure_sketch_len(pot30 - 1, pot30, "pot30- 1");
874 ensure_sketch_len(pot30, pot30, "pot30");
875 ensure_sketch_len(u64::MAX, pot30, "u64::MAX");
876 }
877 };
878 }
879
880 #[test]
881 fn remove_decrements_entry_count() {
882 let mut cache = Cache::new(3);
883 cache.insert("a", "alice");
884 cache.insert("b", "bob");
885 assert_eq!(cache.entry_count(), 2);
886
887 let removed = cache.remove(&"a");
888 assert_eq!(removed, Some("alice"));
889 assert_eq!(cache.entry_count(), 1);
890
891 cache.remove(&"nonexistent");
892 assert_eq!(cache.entry_count(), 1);
893
894 cache.remove(&"b");
895 assert_eq!(cache.entry_count(), 0);
896 }
897
898 #[test]
899 fn invalidate_decrements_entry_count() {
900 let mut cache = Cache::new(3);
901 cache.insert("a", "alice");
902 cache.insert("b", "bob");
903 assert_eq!(cache.entry_count(), 2);
904
905 cache.invalidate(&"a");
906 assert_eq!(cache.entry_count(), 1);
907
908 cache.invalidate(&"nonexistent");
909 assert_eq!(cache.entry_count(), 1);
910
911 cache.invalidate(&"b");
912 assert_eq!(cache.entry_count(), 0);
913 }
914
915 #[test]
916 fn insert_after_remove_on_full_cache() {
917 let mut cache = Cache::new(2);
918 cache.insert("a", "alice");
919 cache.insert("b", "bob");
920 assert_eq!(cache.entry_count(), 2);
921
922 cache.remove(&"a");
923 assert_eq!(cache.entry_count(), 1);
924
925 cache.insert("c", "cindy");
926 assert_eq!(cache.entry_count(), 2);
927 assert_eq!(cache.get(&"c"), Some(&"cindy"));
928 assert_eq!(cache.get(&"b"), Some(&"bob"));
929 assert_eq!(cache.get(&"a"), None);
930 }
931
932 #[test]
933 fn insert_after_invalidate_on_full_cache() {
934 let mut cache = Cache::new(2);
935 cache.insert("a", "alice");
936 cache.insert("b", "bob");
937 assert_eq!(cache.entry_count(), 2);
938
939 cache.invalidate(&"a");
940 assert_eq!(cache.entry_count(), 1);
941
942 cache.insert("c", "cindy");
943 assert_eq!(cache.entry_count(), 2);
944 assert_eq!(cache.get(&"c"), Some(&"cindy"));
945 assert_eq!(cache.get(&"b"), Some(&"bob"));
946 assert_eq!(cache.get(&"a"), None);
947 }
948
949 #[test]
950 fn invalidate_all_panic_safety() {
951 use std::panic::catch_unwind;
952 use std::panic::AssertUnwindSafe;
953 use std::sync::atomic::{AtomicU32, Ordering};
954
955 static DROP_COUNT: AtomicU32 = AtomicU32::new(0);
956
957 struct PanicOnDrop {
958 id: u32,
959 should_panic: bool,
960 }
961
962 impl Drop for PanicOnDrop {
963 fn drop(&mut self) {
964 DROP_COUNT.fetch_add(1, Ordering::Relaxed);
965 if self.should_panic {
966 panic!("intentional panic in drop for id={}", self.id);
967 }
968 }
969 }
970
971 DROP_COUNT.store(0, Ordering::Relaxed);
972 let mut cache = Cache::new(10);
973 cache.insert(
974 1,
975 PanicOnDrop {
976 id: 1,
977 should_panic: false,
978 },
979 );
980 cache.insert(
981 2,
982 PanicOnDrop {
983 id: 2,
984 should_panic: true,
985 },
986 );
987 cache.insert(
988 3,
989 PanicOnDrop {
990 id: 3,
991 should_panic: false,
992 },
993 );
994 assert_eq!(cache.entry_count(), 3);
995
996 let result = catch_unwind(AssertUnwindSafe(|| {
997 cache.invalidate_all();
998 }));
999 assert!(result.is_err());
1000
1001 assert_eq!(cache.entry_count(), 0);
1002 assert_eq!(cache.cache.len(), 0);
1003
1004 cache.insert(
1005 4,
1006 PanicOnDrop {
1007 id: 4,
1008 should_panic: false,
1009 },
1010 );
1011 assert_eq!(cache.entry_count(), 1);
1012 assert!(cache.contains_key(&4));
1013 }
1014
1015 #[test]
1016 fn test_debug_format() {
1017 let mut cache = Cache::new(10);
1018 cache.insert('a', "alice");
1019 cache.insert('b', "bob");
1020 cache.insert('c', "cindy");
1021
1022 let debug_str = format!("{:?}", cache);
1023 assert!(debug_str.starts_with('{'));
1024 assert!(debug_str.contains(r#"'a': "alice""#));
1025 assert!(debug_str.contains(r#"'b': "bob""#));
1026 assert!(debug_str.contains(r#"'c': "cindy""#));
1027 assert!(debug_str.ends_with('}'));
1028 }
1029}