memo_cache/
lib.rs

1#![no_std]
2
3use core::borrow::Borrow;
4
5/// Key equivalence trait, to support `Borrow` types as keys.
6trait Equivalent<K: ?Sized> {
7    /// Returns `true` if two values are equivalent, `false` if otherwise.
8    fn equivalent(&self, k: &K) -> bool;
9}
10
11impl<Q: ?Sized, K: ?Sized> Equivalent<K> for Q
12where
13    Q: Eq,
14    K: Borrow<Q>,
15{
16    fn equivalent(&self, k: &K) -> bool {
17        self == k.borrow()
18    }
19}
20
21/// A single key/value slot used in the cache.
22#[derive(Clone, PartialEq)]
23enum KeyValueSlot<K, V> {
24    Used((K, V)),
25    Empty,
26}
27
28impl<K, V> KeyValueSlot<K, V> {
29    /// Check a used slot key for equivalence.
30    #[cfg_attr(feature = "inline-more", inline)]
31    fn is_key<Q>(&self, k: &Q) -> bool
32    where
33        Q: Equivalent<K> + ?Sized,
34    {
35        if let KeyValueSlot::Used(kv) = self {
36            k.equivalent(&kv.0)
37        } else {
38            false
39        }
40    }
41
42    /// Get the value of a used slot.
43    #[cfg_attr(feature = "inline-more", inline)]
44    fn get_value(&self) -> Option<&V> {
45        if let KeyValueSlot::Used(kv) = self {
46            Some(&kv.1)
47        } else {
48            None
49        }
50    }
51
52    /// Get the value of a used slot (for mutation).
53    #[cfg_attr(feature = "inline-more", inline)]
54    fn get_value_mut(&mut self) -> Option<&mut V> {
55        if let KeyValueSlot::Used(kv) = self {
56            Some(&mut kv.1)
57        } else {
58            None
59        }
60    }
61
62    /// Update the value of a used slot.
63    #[cfg_attr(feature = "inline-more", inline)]
64    fn update_value(&mut self, v: V) {
65        if let KeyValueSlot::Used(kv) = self {
66            kv.1 = v
67        }
68    }
69}
70
71/// A small, fixed-size, heap-allocated key/value cache with retention management.
72pub struct MemoCache<K, V, const SIZE: usize> {
73    buffer: [KeyValueSlot<K, V>; SIZE],
74    cursor: usize,
75}
76
77impl<K, V, const SIZE: usize> MemoCache<K, V, SIZE>
78where
79    K: Clone + Eq,
80    V: Clone,
81{
82    /// Create a new cache.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use memo_cache::MemoCache;
88    ///
89    /// let c = MemoCache::<u32, String, 4>::new();
90    /// ```
91    #[cfg_attr(feature = "inline-more", inline)]
92    pub fn new() -> Self {
93        Self {
94            buffer: [const { KeyValueSlot::Empty }; SIZE],
95            cursor: 0,
96        }
97    }
98
99    /// Get the (fixed) capacity of the cache.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use memo_cache::MemoCache;
105    ///
106    /// let c = MemoCache::<u32, String, 8>::new();
107    ///
108    /// assert_eq!(c.capacity(), 8);
109    /// ```
110    #[cfg_attr(feature = "inline-more", inline)]
111    pub const fn capacity(&self) -> usize {
112        SIZE
113    }
114
115    /// Replace slot under cursor and shift cursor position. Returns a reference to the replaced slot value.
116    #[cfg_attr(feature = "inline-more", inline)]
117    fn replace_and_shift(&mut self, k: K, v: V) -> &V {
118        // SAFETY: The cursor value is assumed to be correct.
119        let s = unsafe { self.buffer.get_unchecked_mut(self.cursor) };
120
121        *s = KeyValueSlot::Used((k, v));
122
123        // Move the cursor over the buffer elements sequentially, creating FIFO behavior.
124        self.cursor = (self.cursor + 1) % SIZE;
125
126        // SAFETY: The slot was filled with a key/value above.
127        unsafe { s.get_value().unwrap_unchecked() }
128    }
129
130    /// Insert a key/value pair.
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// use memo_cache::MemoCache;
136    ///
137    /// let mut c = MemoCache::<u32, &str, 4>::new();
138    ///
139    /// assert_eq!(c.get(&42), None);
140    ///
141    /// c.insert(42, "The Answer");
142    ///
143    /// assert_eq!(c.get(&42), Some(&"The Answer"));
144    /// ```
145    #[cfg_attr(feature = "inline-more", inline)]
146    pub fn insert(&mut self, k: K, v: V) {
147        match self.buffer.iter_mut().find(|e| e.is_key(&k)) {
148            Some(s) => s.update_value(v),
149            None => {
150                self.replace_and_shift(k, v);
151            }
152        }
153    }
154
155    /// Returns `true` if the cache contains a value for the specified key.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// use memo_cache::MemoCache;
161    ///
162    /// let mut c = MemoCache::<u32, &str, 4>::new();
163    ///
164    /// assert_eq!(c.contains_key(&42), false);
165    ///
166    /// c.insert(42, "The Answer");
167    ///
168    /// assert_eq!(c.contains_key(&42), true);
169    /// ```
170    #[cfg_attr(feature = "inline-more", inline)]
171    pub fn contains_key<Q>(&self, k: &Q) -> bool
172    where
173        K: Borrow<Q>,
174        Q: Eq + ?Sized,
175    {
176        self.buffer.iter().any(|e| e.is_key(k))
177    }
178
179    /// Lookup a cache entry by key.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use memo_cache::MemoCache;
185    ///
186    /// let mut c = MemoCache::<u32, &str, 4>::new();
187    ///
188    /// assert_eq!(c.get(&42), None);
189    ///
190    /// c.insert(42, "The Answer");
191    ///
192    /// assert_eq!(c.get(&42), Some(&"The Answer"));
193    /// ```
194    #[cfg_attr(feature = "inline-more", inline)]
195    pub fn get<Q>(&self, k: &Q) -> Option<&V>
196    where
197        K: Borrow<Q>,
198        Q: Eq + ?Sized,
199    {
200        self.buffer
201            .iter()
202            .find(|e| e.is_key(k))
203            .map(|e| e.get_value().unwrap())
204    }
205
206    /// Lookup a cache entry by key (for mutation).
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// use memo_cache::MemoCache;
212    ///
213    /// let mut c = MemoCache::<u32, &str, 4>::new();
214    ///
215    /// c.insert(42, "The Answer");
216    ///
217    /// if let Some(v) = c.get_mut(&42) {
218    ///     *v = "Another Answer";
219    /// }
220    ///
221    /// assert_eq!(c.get(&42), Some(&"Another Answer"));
222    /// ```
223    #[cfg_attr(feature = "inline-more", inline)]
224    pub fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
225    where
226        K: Borrow<Q>,
227        Q: Eq + ?Sized,
228    {
229        self.buffer
230            .iter_mut()
231            .find(|e| e.is_key(k))
232            .map(|e| e.get_value_mut().unwrap())
233    }
234
235    /// Get the index for a given key, if found.
236    #[cfg_attr(feature = "inline-more", inline)]
237    fn get_key_index<Q>(&self, k: &Q) -> Option<usize>
238    where
239        K: Borrow<Q>,
240        Q: Eq + ?Sized,
241    {
242        self.buffer.iter().position(|e| e.is_key(k))
243    }
244
245    /// Get a value, or, if it does not exist in the cache, insert it using the value computed by `f`.
246    /// Returns a reference to the found, or newly inserted value associated with the given key.
247    /// If a value is inserted, the key is cloned.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use memo_cache::MemoCache;
253    ///
254    /// let mut c = MemoCache::<u32, &str, 4>::new();
255    ///
256    /// assert_eq!(c.get(&42), None);
257    ///
258    /// let v = c.get_or_insert_with(&42, |_| "The Answer");
259    ///
260    /// assert_eq!(v, &"The Answer");
261    /// assert_eq!(c.get(&42), Some(&"The Answer"));
262    /// ```
263    ///
264    /// # Notes
265    ///
266    /// Because this crate is `no_std`, we have no access to `std::borrow::ToOwned`, which means we cannot create a
267    /// version of `get_or_insert_with` that can create an owned value from a borrowed key.
268    ///
269    #[cfg_attr(feature = "inline-more", inline)]
270    pub fn get_or_insert_with<F>(&mut self, k: &K, f: F) -> &V
271    where
272        F: FnOnce(&K) -> V,
273    {
274        if let Some(i) = self.get_key_index(k) {
275            // SAFETY: The key index was retrieved from a found key.
276            unsafe { self.buffer[i].get_value().unwrap_unchecked() }
277        } else {
278            self.replace_and_shift(k.clone(), f(k))
279        }
280    }
281
282    /// Get a value, or, if it does not exist in the cache, insert it using the value computed by `f`.
283    /// Returns a result with a reference to the found, or newly inserted value associated with the given key.
284    /// If `f` fails, the error is returned.
285    /// If a value is inserted, the key is cloned.
286    ///
287    /// # Examples
288    ///
289    /// ```
290    /// use memo_cache::MemoCache;
291    ///
292    /// let mut c = MemoCache::<u32, &str, 4>::new();
293    ///
294    /// assert_eq!(c.get(&42), None);
295    ///
296    /// let answer : Result<_, &str> = Ok("The Answer");
297    /// let v = c.get_or_try_insert_with(&42, |_| answer);
298    ///
299    /// assert_eq!(v, Ok(&"The Answer"));
300    /// assert_eq!(c.get(&42), Some(&"The Answer"));
301    ///
302    /// let v = c.get_or_try_insert_with(&17, |_| Err("Dunno"));
303    ///
304    /// assert_eq!(v, Err("Dunno"));
305    /// assert_eq!(c.get(&17), None);
306    /// ```
307    ///
308    /// # Notes
309    ///
310    /// Because this crate is `no_std`, we have no access to `std::borrow::ToOwned`, which means we cannot create a
311    /// version of `get_or_try_insert_with` that can create an owned value from a borrowed key.
312    ///
313    #[cfg_attr(feature = "inline-more", inline)]
314    pub fn get_or_try_insert_with<F, E>(&mut self, k: &K, f: F) -> Result<&V, E>
315    where
316        F: FnOnce(&K) -> Result<V, E>,
317    {
318        if let Some(i) = self.get_key_index(k) {
319            // SAFETY: The key index was retrieved from a found key.
320            Ok(unsafe { self.buffer[i].get_value().unwrap_unchecked() })
321        } else {
322            f(k).map(|v| self.replace_and_shift(k.clone(), v))
323        }
324    }
325
326    /// Clear the cache.
327    ///
328    /// # Examples
329    ///
330    /// ```
331    /// use memo_cache::MemoCache;
332    ///
333    /// let mut c = MemoCache::<u32, &str, 4>::new();
334    ///
335    /// assert_eq!(c.get(&42), None);
336    ///
337    /// c.insert(42, "The Answer");
338    ///
339    /// assert_eq!(c.get(&42), Some(&"The Answer"));
340    ///
341    /// c.clear();
342    ///
343    /// assert_eq!(c.get(&42), None);
344    ///
345    #[cfg_attr(feature = "inline-more", inline)]
346    pub fn clear(&mut self) {
347        self.buffer
348            .iter_mut()
349            .for_each(|e| *e = KeyValueSlot::Empty);
350        self.cursor = 0;
351    }
352}
353
354impl<K, V, const SIZE: usize> Default for MemoCache<K, V, SIZE>
355where
356    K: Clone + Eq,
357    V: Clone,
358{
359    fn default() -> Self {
360        Self::new()
361    }
362}
363
364#[cfg(test)]
365mod tests_internal {
366    use super::*;
367
368    #[test]
369    fn test_new_state() {
370        const SIZE: usize = 8;
371
372        let c = MemoCache::<i32, i32, SIZE>::new();
373
374        // Verify cache size.
375        assert_eq!(c.buffer.len(), SIZE);
376        assert_eq!(c.capacity(), SIZE);
377
378        // All slots should be empty.
379        assert!(c.buffer.iter().all(|s| s == &KeyValueSlot::Empty));
380    }
381
382    #[test]
383    fn test_cursor_state() {
384        let mut c = MemoCache::<i32, i32, 2>::new();
385
386        assert_eq!(c.cursor, 0);
387
388        c.insert(1, 2);
389
390        assert_eq!(c.cursor, 1);
391
392        c.insert(3, 4);
393
394        assert_eq!(c.cursor, 0);
395
396        c.insert(5, 6);
397
398        assert_eq!(c.cursor, 1);
399
400        c.insert(7, 8);
401
402        assert_eq!(c.cursor, 0);
403    }
404}