associative_cache/entry.rs
1//! An API for get-or-create operations on cache entries, similar to
2//! `std::collections::HashMap`'s entry API.
3
4use super::*;
5use std::fmt;
6
7/// A potentially-empty entry in a cache, used to perform get-or-create
8/// operations on the cache.
9///
10/// Constructed via the `AssociativeCache::entry` method.
11pub struct Entry<'a, K, V, C, I, R>
12where
13 C: Capacity,
14 R: Replacement<V, C>,
15{
16 pub(crate) cache: &'a mut AssociativeCache<K, V, C, I, R>,
17 pub(crate) index: usize,
18 pub(crate) kind: EntryKind,
19}
20
21impl<'a, K, V, C, I, R> fmt::Debug for Entry<'a, K, V, C, I, R>
22where
23 C: Capacity,
24 R: Replacement<V, C>,
25{
26 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 let Entry {
28 cache: _,
29 ref index,
30 ref kind,
31 } = self;
32 f.debug_struct("Entry")
33 .field("index", index)
34 .field("kind", kind)
35 .finish()
36 }
37}
38
39#[derive(Debug)]
40pub(crate) enum EntryKind {
41 // The index is occupied with a cache entry for this key.
42 Occupied,
43 // The index is for a slot that has no entry in it.
44 Vacant,
45 // The index is for a slot that has a to-be-replaced entry for a
46 // different key.
47 Replace,
48}
49
50impl<'a, K, V, C, I, R> Entry<'a, K, V, C, I, R>
51where
52 C: Capacity,
53 I: Indices<K, C>,
54 R: Replacement<V, C>,
55{
56 /// Get the underlying cached data, creating and inserting it into the cache
57 /// if it doesn't already exist.
58 ///
59 /// ## Differences from `std::collections::HashMap`'s `Entry` API
60 ///
61 /// `std::collections::HashMap`'s `Entry` API takes unconditional ownership
62 /// of the query key, even in scenarios where there is already an entry with
63 /// that key in the map. This means that if your keys are expensive to
64 /// create (like `String` and its heap allocation) that you have to eagerly
65 /// construct the key even if you don't end up needing it.
66 ///
67 /// In contrast, the `associative_cache::Entry` API allows you to get an
68 /// `Entry` with just a borrow of a key, allowing you to delay the
69 /// potentially-expensive key construction until we actually need
70 /// it. However, this is not without drawbacks. Now the `or_insert_with`
71 /// method needs a way to construct an owned key: the `make_key` parameter
72 /// here. **`make_key` must return an owned key that is equivalent to the
73 /// borrowed key that was used to get this `Entry`.** Failure to do this
74 /// will result in an invalid cache (likely manifesting as wasted entries
75 /// that take up space but can't ever be queried for).
76 ///
77 /// # Example
78 ///
79 /// ```
80 /// use associative_cache::*;
81 ///
82 /// let mut cache = AssociativeCache::<
83 /// String,
84 /// usize,
85 /// Capacity4,
86 /// HashTwoWay,
87 /// RoundRobinReplacement,
88 /// >::default();
89 ///
90 /// // Get or create an entry for "hi", delaying the `&str` to `String`
91 /// // allocation until if/when we actually insert into the cache.
92 /// let val = cache.entry("hi").or_insert_with(
93 /// || "hi".to_string(),
94 /// || 42,
95 /// );
96 ///
97 /// // The cache was empty, so we inserted the default value of 42.
98 /// assert_eq!(*val, 42);
99 ///
100 /// // We can modify the value.
101 /// *val += 1;
102 /// ```
103 #[inline]
104 pub fn or_insert_with(
105 self,
106 make_key: impl FnOnce() -> K,
107 make_val: impl FnOnce() -> V,
108 ) -> &'a mut V {
109 assert!(self.index < C::CAPACITY);
110 match self.kind {
111 EntryKind::Occupied => match &mut self.cache.entries[self.index] {
112 Some((_, v)) => v,
113 _ => unreachable!(),
114 },
115 EntryKind::Vacant | EntryKind::Replace => {
116 if let EntryKind::Vacant = self.kind {
117 self.cache.len += 1;
118 }
119 self.cache.entries[self.index] = Some((make_key(), make_val()));
120 match &mut self.cache.entries[self.index] {
121 Some((_, v)) => {
122 self.cache.replacement_policy.on_insert(v);
123 v
124 }
125 _ => unreachable!(),
126 }
127 }
128 }
129 }
130
131 /// If inserting into this `Entry` will replace another entry in the
132 /// cache, remove that other entry from the cache and return it now.
133 ///
134 /// # Example
135 ///
136 /// ```
137 /// use associative_cache::*;
138 ///
139 /// let mut cache = AssociativeCache::<
140 /// String,
141 /// usize,
142 /// Capacity256,
143 /// HashTwoWay,
144 /// RoundRobinReplacement,
145 /// >::default();
146 ///
147 /// cache.insert("hi".to_string(), 5);
148 ///
149 /// let mut entry = cache.entry("bye");
150 ///
151 /// // Because this entry could replace the entry for "hi" depending on the hash
152 /// // function in use, we have an opportunity to recover the
153 /// // about-to-be-replaced entry here.
154 /// if let Some((key, val)) = entry.take_entry_that_will_be_replaced() {
155 /// assert_eq!(key, "hi");
156 /// assert_eq!(val, 5);
157 /// }
158 ///
159 /// let val = entry.or_insert_with(|| "bye".into(), || 1337);
160 /// assert_eq!(*val, 1337);
161 /// ```
162 #[inline]
163 pub fn take_entry_that_will_be_replaced(&mut self) -> Option<(K, V)> {
164 assert!(self.index < C::CAPACITY);
165 if let EntryKind::Replace = self.kind {
166 self.cache.len -= 1;
167 self.kind = EntryKind::Vacant;
168 mem::replace(&mut self.cache.entries[self.index], None)
169 } else {
170 None
171 }
172 }
173}