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_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 drop(old_cache);
321 }
322
323 #[allow(clippy::needless_collect)]
338 pub fn invalidate_entries_if(&mut self, mut predicate: impl FnMut(&K, &V) -> bool) {
339 let Self { cache, deques, .. } = self;
340
341 let keys_to_invalidate = cache
347 .iter()
348 .filter(|(key, entry)| (predicate)(key, &entry.value))
349 .map(|(key, _)| Rc::clone(key))
350 .collect::<Vec<_>>();
351
352 let mut invalidated = 0u64;
353
354 keys_to_invalidate.into_iter().for_each(|k| {
355 if let Some(mut entry) = cache.remove(&k) {
356 let _weight = entry.policy_weight();
357 deques.unlink_ao(&mut entry);
358 invalidated += 1;
359 }
360 });
361 self.entry_count -= invalidated;
362 }
363
364 pub fn iter(&self) -> Iter<'_, K, V> {
387 Iter::new(self, self.cache.iter())
388 }
389}
390
391impl<K, V, S> Cache<K, V, S>
395where
396 K: Hash + Eq,
397 S: BuildHasher + Clone,
398{
399 #[inline]
400 fn hash<Q>(&self, key: &Q) -> u64
401 where
402 Rc<K>: Borrow<Q>,
403 Q: Hash + Eq + ?Sized,
404 {
405 self.build_hasher.hash_one(key)
406 }
407
408 fn record_hit(deques: &mut Deques<K>, entry: &mut ValueEntry<K, V>) {
409 deques.move_to_back_ao(entry)
410 }
411
412 fn has_enough_capacity(&self, candidate_weight: u32, ws: u64) -> bool {
413 self.max_capacity
414 .map(|limit| ws + candidate_weight as u64 <= limit)
415 .unwrap_or(true)
416 }
417
418 fn weights_to_evict(&self) -> u64 {
419 self.max_capacity
420 .map(|limit| self.entry_count.saturating_sub(limit))
421 .unwrap_or_default()
422 }
423
424 #[inline]
425 fn should_enable_frequency_sketch(&self) -> bool {
426 if self.frequency_sketch_enabled {
427 false
428 } else if let Some(max_cap) = self.max_capacity {
429 self.entry_count >= max_cap / 2
430 } else {
431 false
432 }
433 }
434
435 #[inline]
436 fn enable_frequency_sketch(&mut self) {
437 if let Some(max_cap) = self.max_capacity {
438 self.do_enable_frequency_sketch(max_cap);
439 }
440 }
441
442 #[cfg(test)]
443 fn enable_frequency_sketch_for_testing(&mut self) {
444 if let Some(max_cap) = self.max_capacity {
445 self.do_enable_frequency_sketch(max_cap);
446 }
447 }
448
449 #[inline]
450 fn do_enable_frequency_sketch(&mut self, cache_capacity: u64) {
451 let skt_capacity = common::sketch_capacity(cache_capacity);
452 self.frequency_sketch.ensure_capacity(skt_capacity);
453 self.frequency_sketch_enabled = true;
454 }
455
456 #[inline]
457 fn handle_insert(&mut self, key: Rc<K>, hash: u64, policy_weight: u32) {
458 let has_free_space = self.has_enough_capacity(policy_weight, self.entry_count);
459 let (cache, deqs, freq) = (&mut self.cache, &mut self.deques, &self.frequency_sketch);
460
461 if has_free_space {
462 let key = Rc::clone(&key);
464 let entry = cache.get_mut(&key).unwrap();
465 deqs.push_back_ao(
466 CacheRegion::MainProbation,
467 KeyHashDate::new(Rc::clone(&key), hash),
468 entry,
469 );
470 self.entry_count += 1;
471 if self.should_enable_frequency_sketch() {
474 self.enable_frequency_sketch();
475 }
476
477 return;
478 }
479
480 if let Some(max) = self.max_capacity {
481 if policy_weight as u64 > max {
482 cache.remove(&Rc::clone(&key));
484 return;
485 }
486 }
487
488 let mut candidate = EntrySizeAndFrequency::new(policy_weight as u64);
489 candidate.add_frequency(freq, hash);
490
491 match Self::admit(&candidate, cache, deqs, freq) {
492 AdmissionResult::Admitted { victim_nodes } => {
493 for victim in victim_nodes {
495 let mut vic_entry = cache
497 .remove(unsafe { &victim.as_ref().element.key })
498 .expect("Cannot remove a victim from the hash map");
499 deqs.unlink_ao(&mut vic_entry);
501 self.entry_count -= 1;
503 }
504
505 let entry = cache.get_mut(&key).unwrap();
507 let key = Rc::clone(&key);
508 deqs.push_back_ao(
509 CacheRegion::MainProbation,
510 KeyHashDate::new(Rc::clone(&key), hash),
511 entry,
512 );
513
514 self.entry_count += 1;
515 if self.should_enable_frequency_sketch() {
519 self.enable_frequency_sketch();
520 }
521 }
522 AdmissionResult::Rejected => {
523 cache.remove(&key);
525 }
526 }
527 }
528
529 #[inline]
547 fn admit(
548 candidate: &EntrySizeAndFrequency,
549 _cache: &CacheStore<K, V, S>,
550 deqs: &Deques<K>,
551 freq: &FrequencySketch,
552 ) -> AdmissionResult<K> {
553 let mut victims = EntrySizeAndFrequency::default();
554 let mut victim_nodes = SmallVec::default();
555
556 let mut next_victim = deqs.probation.peek_front_ptr();
558
559 while victims.weight < candidate.weight {
561 if candidate.freq < victims.freq {
562 break;
563 }
564 if let Some(victim) = next_victim.take() {
565 next_victim = DeqNode::next_node_ptr(victim);
566 let vic_elem = &unsafe { victim.as_ref() }.element;
567
568 victims.add_policy_weight();
572 victims.add_frequency(freq, vic_elem.hash);
573 victim_nodes.push(victim);
574 } else {
575 break;
577 }
578 }
579
580 if victims.weight >= candidate.weight && candidate.freq > victims.freq {
586 AdmissionResult::Admitted { victim_nodes }
587 } else {
588 AdmissionResult::Rejected
589 }
590 }
591
592 fn handle_update(&mut self, key: Rc<K>, policy_weight: u32, old_entry: ValueEntry<K, V>) {
593 let entry = self.cache.get_mut(&key).unwrap();
594 entry.replace_deq_nodes_with(old_entry);
595 entry.set_policy_weight(policy_weight);
596
597 let deqs = &mut self.deques;
598 deqs.move_to_back_ao(entry);
599
600 }
603
604 #[inline]
605 fn evict_lru_entries(&mut self) {
606 const DEQ_NAME: &str = "probation";
607
608 let weights_to_evict = self.weights_to_evict();
609 let mut evicted_count = 0u64;
610 let mut evicted_policy_weight = 0u64;
611
612 {
613 let deqs = &mut self.deques;
614 let (probation, cache) = (&mut deqs.probation, &mut self.cache);
615
616 for _ in 0..EVICTION_BATCH_SIZE {
617 if evicted_policy_weight >= weights_to_evict {
618 break;
619 }
620
621 #[allow(clippy::map_clone)]
624 let key = probation
625 .peek_front()
626 .map(|node| Rc::clone(&node.element.key));
627
628 if key.is_none() {
629 break;
630 }
631 let key = key.unwrap();
632
633 if let Some(mut entry) = cache.remove(&key) {
634 let weight = entry.policy_weight();
635 Deques::unlink_ao_from_deque(DEQ_NAME, probation, &mut entry);
636 evicted_count += 1;
637 evicted_policy_weight = evicted_policy_weight.saturating_add(weight as u64);
638 } else {
639 probation.pop_front();
640 }
641 }
642 }
643
644 self.entry_count -= evicted_count;
645 }
647}
648
649#[cfg(test)]
653impl<K, V, S> Cache<K, V, S>
654where
655 K: Hash + Eq,
656 S: BuildHasher + Clone,
657{
658}
659
660#[derive(Default)]
661struct EntrySizeAndFrequency {
662 weight: u64,
663 freq: u32,
664}
665
666impl EntrySizeAndFrequency {
667 fn new(policy_weight: u64) -> Self {
668 Self {
669 weight: policy_weight,
670 ..Default::default()
671 }
672 }
673
674 fn add_policy_weight(&mut self) {
675 self.weight += 1;
676 }
677
678 fn add_frequency(&mut self, freq: &FrequencySketch, hash: u64) {
679 self.freq += freq.frequency(hash) as u32;
680 }
681}
682
683type AoqNode<K> = NonNull<DeqNode<KeyHashDate<K>>>;
685
686enum AdmissionResult<K> {
687 Admitted {
688 victim_nodes: SmallVec<[AoqNode<K>; 8]>,
689 },
690 Rejected,
691}
692
693#[cfg(test)]
699mod tests {
700 use super::Cache;
701
702 #[test]
703 fn basic_single_thread() {
704 let mut cache = Cache::new(3);
705 cache.enable_frequency_sketch_for_testing();
706
707 cache.insert("a", "alice");
708 cache.insert("b", "bob");
709 assert_eq!(cache.get(&"a"), Some(&"alice"));
710 assert!(cache.contains_key(&"a"));
711 assert!(cache.contains_key(&"b"));
712 assert_eq!(cache.get(&"b"), Some(&"bob"));
713 cache.insert("c", "cindy");
716 assert_eq!(cache.get(&"c"), Some(&"cindy"));
717 assert!(cache.contains_key(&"c"));
718 assert!(cache.contains_key(&"a"));
721 assert_eq!(cache.get(&"a"), Some(&"alice"));
722 assert_eq!(cache.get(&"b"), Some(&"bob"));
723 assert!(cache.contains_key(&"b"));
724 cache.insert("d", "david"); assert_eq!(cache.get(&"d"), None); assert!(!cache.contains_key(&"d"));
730
731 cache.insert("d", "david");
732 assert!(!cache.contains_key(&"d"));
733 assert_eq!(cache.get(&"d"), None); cache.insert("d", "dennis");
738 assert_eq!(cache.get(&"a"), Some(&"alice"));
739 assert_eq!(cache.get(&"b"), Some(&"bob"));
740 assert_eq!(cache.get(&"c"), None);
741 assert_eq!(cache.get(&"d"), Some(&"dennis"));
742 assert!(cache.contains_key(&"a"));
743 assert!(cache.contains_key(&"b"));
744 assert!(!cache.contains_key(&"c"));
745 assert!(cache.contains_key(&"d"));
746
747 cache.invalidate(&"b");
748 assert_eq!(cache.get(&"b"), None);
749 assert!(!cache.contains_key(&"b"));
750 }
751
752 #[test]
753 fn invalidate_all() {
754 let mut cache = Cache::new(100);
755 cache.enable_frequency_sketch_for_testing();
756
757 cache.insert("a", "alice");
758 cache.insert("b", "bob");
759 cache.insert("c", "cindy");
760 assert_eq!(cache.get(&"a"), Some(&"alice"));
761 assert_eq!(cache.get(&"b"), Some(&"bob"));
762 assert_eq!(cache.get(&"c"), Some(&"cindy"));
763 assert!(cache.contains_key(&"a"));
764 assert!(cache.contains_key(&"b"));
765 assert!(cache.contains_key(&"c"));
766
767 cache.invalidate_all();
768
769 cache.insert("d", "david");
770
771 assert!(cache.get(&"a").is_none());
772 assert!(cache.get(&"b").is_none());
773 assert!(cache.get(&"c").is_none());
774 assert_eq!(cache.get(&"d"), Some(&"david"));
775 assert!(!cache.contains_key(&"a"));
776 assert!(!cache.contains_key(&"b"));
777 assert!(!cache.contains_key(&"c"));
778 assert!(cache.contains_key(&"d"));
779 }
780
781 #[test]
782 fn invalidate_entries_if() {
783 use std::collections::HashSet;
784
785 let mut cache = Cache::new(100);
786 cache.enable_frequency_sketch_for_testing();
787
788 cache.insert(0, "alice");
789 cache.insert(1, "bob");
790 cache.insert(2, "alex");
791
792 assert_eq!(cache.get(&0), Some(&"alice"));
793 assert_eq!(cache.get(&1), Some(&"bob"));
794 assert_eq!(cache.get(&2), Some(&"alex"));
795 assert!(cache.contains_key(&0));
796 assert!(cache.contains_key(&1));
797 assert!(cache.contains_key(&2));
798
799 let names = ["alice", "alex"].iter().cloned().collect::<HashSet<_>>();
800 cache.invalidate_entries_if(move |_k, &v| names.contains(v));
801
802 cache.insert(3, "alice");
803
804 assert!(cache.get(&0).is_none());
805 assert!(cache.get(&2).is_none());
806 assert_eq!(cache.get(&1), Some(&"bob"));
807 assert_eq!(cache.get(&3), Some(&"alice"));
809
810 assert!(!cache.contains_key(&0));
811 assert!(cache.contains_key(&1));
812 assert!(!cache.contains_key(&2));
813 assert!(cache.contains_key(&3));
814
815 assert_eq!(cache.cache.len(), 2);
816
817 cache.invalidate_entries_if(|_k, &v| v == "alice");
818 cache.invalidate_entries_if(|_k, &v| v == "bob");
819
820 assert!(cache.get(&1).is_none());
821 assert!(cache.get(&3).is_none());
822
823 assert!(!cache.contains_key(&1));
824 assert!(!cache.contains_key(&3));
825
826 assert_eq!(cache.cache.len(), 0);
827 }
828
829 #[cfg_attr(target_pointer_width = "16", ignore)]
830 #[test]
831 fn test_skt_capacity_will_not_overflow() {
832 let pot = |exp| 2u64.pow(exp);
834
835 let ensure_sketch_len = |max_capacity, len, name| {
836 let mut cache = Cache::<u8, u8>::new(max_capacity);
837 cache.enable_frequency_sketch_for_testing();
838 assert_eq!(cache.frequency_sketch.table_len(), len as usize, "{}", name);
839 };
840
841 if cfg!(target_pointer_width = "32") {
842 let pot24 = pot(24);
843 let pot16 = pot(16);
844 ensure_sketch_len(0, 128, "0");
845 ensure_sketch_len(128, 128, "128");
846 ensure_sketch_len(pot16, pot16, "pot16");
847 ensure_sketch_len(pot16 + 1, pot(17), "pot16 + 1");
849 ensure_sketch_len(pot24 - 1, pot24, "pot24 - 1");
851 ensure_sketch_len(pot24, pot24, "pot24");
852 ensure_sketch_len(pot(27), pot24, "pot(27)");
853 ensure_sketch_len(u32::MAX as u64, pot24, "u32::MAX");
854 } else {
855 let pot30 = pot(30);
857 let pot16 = pot(16);
858 ensure_sketch_len(0, 128, "0");
859 ensure_sketch_len(128, 128, "128");
860 ensure_sketch_len(pot16, pot16, "pot16");
861 ensure_sketch_len(pot16 + 1, pot(17), "pot16 + 1");
863
864 if !cfg!(circleci) {
867 ensure_sketch_len(pot30 - 1, pot30, "pot30- 1");
869 ensure_sketch_len(pot30, pot30, "pot30");
870 ensure_sketch_len(u64::MAX, pot30, "u64::MAX");
871 }
872 };
873 }
874
875 #[test]
876 fn remove_decrements_entry_count() {
877 let mut cache = Cache::new(3);
878 cache.insert("a", "alice");
879 cache.insert("b", "bob");
880 assert_eq!(cache.entry_count(), 2);
881
882 let removed = cache.remove(&"a");
883 assert_eq!(removed, Some("alice"));
884 assert_eq!(cache.entry_count(), 1);
885
886 cache.remove(&"nonexistent");
887 assert_eq!(cache.entry_count(), 1);
888
889 cache.remove(&"b");
890 assert_eq!(cache.entry_count(), 0);
891 }
892
893 #[test]
894 fn invalidate_decrements_entry_count() {
895 let mut cache = Cache::new(3);
896 cache.insert("a", "alice");
897 cache.insert("b", "bob");
898 assert_eq!(cache.entry_count(), 2);
899
900 cache.invalidate(&"a");
901 assert_eq!(cache.entry_count(), 1);
902
903 cache.invalidate(&"nonexistent");
904 assert_eq!(cache.entry_count(), 1);
905
906 cache.invalidate(&"b");
907 assert_eq!(cache.entry_count(), 0);
908 }
909
910 #[test]
911 fn insert_after_remove_on_full_cache() {
912 let mut cache = Cache::new(2);
913 cache.insert("a", "alice");
914 cache.insert("b", "bob");
915 assert_eq!(cache.entry_count(), 2);
916
917 cache.remove(&"a");
918 assert_eq!(cache.entry_count(), 1);
919
920 cache.insert("c", "cindy");
921 assert_eq!(cache.entry_count(), 2);
922 assert_eq!(cache.get(&"c"), Some(&"cindy"));
923 assert_eq!(cache.get(&"b"), Some(&"bob"));
924 assert_eq!(cache.get(&"a"), None);
925 }
926
927 #[test]
928 fn insert_after_invalidate_on_full_cache() {
929 let mut cache = Cache::new(2);
930 cache.insert("a", "alice");
931 cache.insert("b", "bob");
932 assert_eq!(cache.entry_count(), 2);
933
934 cache.invalidate(&"a");
935 assert_eq!(cache.entry_count(), 1);
936
937 cache.insert("c", "cindy");
938 assert_eq!(cache.entry_count(), 2);
939 assert_eq!(cache.get(&"c"), Some(&"cindy"));
940 assert_eq!(cache.get(&"b"), Some(&"bob"));
941 assert_eq!(cache.get(&"a"), None);
942 }
943
944 #[test]
945 fn invalidate_all_panic_safety() {
946 use std::panic::catch_unwind;
947 use std::panic::AssertUnwindSafe;
948 use std::sync::atomic::{AtomicU32, Ordering};
949
950 static DROP_COUNT: AtomicU32 = AtomicU32::new(0);
951
952 struct PanicOnDrop {
953 id: u32,
954 should_panic: bool,
955 }
956
957 impl Drop for PanicOnDrop {
958 fn drop(&mut self) {
959 DROP_COUNT.fetch_add(1, Ordering::Relaxed);
960 if self.should_panic {
961 panic!("intentional panic in drop for id={}", self.id);
962 }
963 }
964 }
965
966 DROP_COUNT.store(0, Ordering::Relaxed);
967 let mut cache = Cache::new(10);
968 cache.insert(
969 1,
970 PanicOnDrop {
971 id: 1,
972 should_panic: false,
973 },
974 );
975 cache.insert(
976 2,
977 PanicOnDrop {
978 id: 2,
979 should_panic: true,
980 },
981 );
982 cache.insert(
983 3,
984 PanicOnDrop {
985 id: 3,
986 should_panic: false,
987 },
988 );
989 assert_eq!(cache.entry_count(), 3);
990
991 let result = catch_unwind(AssertUnwindSafe(|| {
992 cache.invalidate_all();
993 }));
994 assert!(result.is_err());
995
996 assert_eq!(cache.entry_count(), 0);
997 assert_eq!(cache.cache.len(), 0);
998
999 cache.insert(
1000 4,
1001 PanicOnDrop {
1002 id: 4,
1003 should_panic: false,
1004 },
1005 );
1006 assert_eq!(cache.entry_count(), 1);
1007 assert!(cache.contains_key(&4));
1008 }
1009
1010 #[test]
1011 fn test_debug_format() {
1012 let mut cache = Cache::new(10);
1013 cache.insert('a', "alice");
1014 cache.insert('b', "bob");
1015 cache.insert('c', "cindy");
1016
1017 let debug_str = format!("{:?}", cache);
1018 assert!(debug_str.starts_with('{'));
1019 assert!(debug_str.contains(r#"'a': "alice""#));
1020 assert!(debug_str.contains(r#"'b': "bob""#));
1021 assert!(debug_str.contains(r#"'c': "cindy""#));
1022 assert!(debug_str.ends_with('}'));
1023 }
1024}