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}