cached/stores/
unbound.rs

1use super::Cached;
2
3use std::cmp::Eq;
4use std::hash::Hash;
5
6#[cfg(feature = "ahash")]
7use hashbrown::{hash_map::Entry, HashMap};
8
9#[cfg(not(feature = "ahash"))]
10use std::collections::{hash_map::Entry, HashMap};
11
12#[cfg(feature = "async")]
13use {super::CachedAsync, async_trait::async_trait, futures::Future};
14
15/// Default unbounded cache
16///
17/// This cache has no size limit or eviction policy.
18///
19/// Note: This cache is in-memory only
20#[derive(Clone, Debug)]
21pub struct UnboundCache<K, V> {
22    pub(super) store: HashMap<K, V>,
23    pub(super) hits: u64,
24    pub(super) misses: u64,
25    pub(super) initial_capacity: Option<usize>,
26}
27
28impl<K, V> PartialEq for UnboundCache<K, V>
29where
30    K: Eq + Hash,
31    V: PartialEq,
32{
33    fn eq(&self, other: &UnboundCache<K, V>) -> bool {
34        self.store.eq(&other.store)
35    }
36}
37
38impl<K, V> Eq for UnboundCache<K, V>
39where
40    K: Eq + Hash,
41    V: PartialEq,
42{
43}
44
45impl<K: Hash + Eq, V> UnboundCache<K, V> {
46    /// Creates an empty `UnboundCache`
47    #[allow(clippy::new_without_default)]
48    #[must_use]
49    pub fn new() -> UnboundCache<K, V> {
50        UnboundCache {
51            store: Self::new_store(None),
52            hits: 0,
53            misses: 0,
54            initial_capacity: None,
55        }
56    }
57
58    /// Creates an empty `UnboundCache` with a given pre-allocated capacity
59    #[must_use]
60    pub fn with_capacity(size: usize) -> UnboundCache<K, V> {
61        UnboundCache {
62            store: Self::new_store(Some(size)),
63            hits: 0,
64            misses: 0,
65            initial_capacity: Some(size),
66        }
67    }
68
69    fn new_store(capacity: Option<usize>) -> HashMap<K, V> {
70        capacity.map_or_else(HashMap::new, HashMap::with_capacity)
71    }
72
73    /// Returns a reference to the cache's `store`
74    #[must_use]
75    pub fn get_store(&self) -> &HashMap<K, V> {
76        &self.store
77    }
78}
79
80impl<K: Hash + Eq, V> Cached<K, V> for UnboundCache<K, V> {
81    fn cache_get<Q>(&mut self, key: &Q) -> Option<&V>
82    where
83        K: std::borrow::Borrow<Q>,
84        Q: std::hash::Hash + Eq + ?Sized,
85    {
86        if let Some(v) = self.store.get(key) {
87            self.hits += 1;
88            Some(v)
89        } else {
90            self.misses += 1;
91            None
92        }
93    }
94    fn cache_get_mut<Q>(&mut self, key: &Q) -> std::option::Option<&mut V>
95    where
96        K: std::borrow::Borrow<Q>,
97        Q: std::hash::Hash + Eq + ?Sized,
98    {
99        if let Some(v) = self.store.get_mut(key) {
100            self.hits += 1;
101            Some(v)
102        } else {
103            self.misses += 1;
104            None
105        }
106    }
107    fn cache_set(&mut self, key: K, val: V) -> Option<V> {
108        self.store.insert(key, val)
109    }
110    fn cache_get_or_set_with<F: FnOnce() -> V>(&mut self, key: K, f: F) -> &mut V {
111        match self.store.entry(key) {
112            Entry::Occupied(occupied) => {
113                self.hits += 1;
114                occupied.into_mut()
115            }
116
117            Entry::Vacant(vacant) => {
118                self.misses += 1;
119                vacant.insert(f())
120            }
121        }
122    }
123    fn cache_try_get_or_set_with<F: FnOnce() -> Result<V, E>, E>(
124        &mut self,
125        k: K,
126        f: F,
127    ) -> Result<&mut V, E> {
128        match self.store.entry(k) {
129            Entry::Occupied(occupied) => {
130                self.hits += 1;
131                Ok(occupied.into_mut())
132            }
133
134            Entry::Vacant(vacant) => {
135                self.misses += 1;
136                Ok(vacant.insert(f()?))
137            }
138        }
139    }
140    fn cache_remove<Q>(&mut self, k: &Q) -> Option<V>
141    where
142        K: std::borrow::Borrow<Q>,
143        Q: std::hash::Hash + Eq + ?Sized,
144    {
145        self.store.remove(k)
146    }
147    fn cache_clear(&mut self) {
148        self.store.clear();
149    }
150    fn cache_reset(&mut self) {
151        self.store = Self::new_store(self.initial_capacity);
152    }
153    fn cache_reset_metrics(&mut self) {
154        self.misses = 0;
155        self.hits = 0;
156    }
157    fn cache_size(&self) -> usize {
158        self.store.len()
159    }
160    fn cache_hits(&self) -> Option<u64> {
161        Some(self.hits)
162    }
163    fn cache_misses(&self) -> Option<u64> {
164        Some(self.misses)
165    }
166}
167
168#[cfg(feature = "async")]
169#[async_trait]
170impl<K, V> CachedAsync<K, V> for UnboundCache<K, V>
171where
172    K: Hash + Eq + Clone + Send,
173{
174    async fn get_or_set_with<F, Fut>(&mut self, key: K, f: F) -> &mut V
175    where
176        V: Send,
177        F: FnOnce() -> Fut + Send,
178        Fut: Future<Output = V> + Send,
179    {
180        match self.store.entry(key) {
181            Entry::Occupied(occupied) => {
182                self.hits += 1;
183                occupied.into_mut()
184            }
185
186            Entry::Vacant(vacant) => {
187                self.misses += 1;
188                vacant.insert(f().await)
189            }
190        }
191    }
192
193    async fn try_get_or_set_with<F, Fut, E>(&mut self, key: K, f: F) -> Result<&mut V, E>
194    where
195        V: Send,
196        F: FnOnce() -> Fut + Send,
197        Fut: Future<Output = Result<V, E>> + Send,
198    {
199        let v = match self.store.entry(key) {
200            Entry::Occupied(occupied) => {
201                self.hits += 1;
202                occupied.into_mut()
203            }
204
205            Entry::Vacant(vacant) => {
206                self.misses += 1;
207                vacant.insert(f().await?)
208            }
209        };
210        Ok(v)
211    }
212}
213
214#[cfg(test)]
215/// Cache store tests
216mod tests {
217    use super::*;
218
219    #[test]
220    fn basic_cache() {
221        let mut c = UnboundCache::new();
222        assert!(c.cache_get(&1).is_none());
223        let misses = c.cache_misses().unwrap();
224        assert_eq!(1, misses);
225
226        assert_eq!(c.cache_set(1, 100), None);
227        assert!(c.cache_get(&1).is_some());
228        let hits = c.cache_hits().unwrap();
229        let misses = c.cache_misses().unwrap();
230        assert_eq!(1, hits);
231        assert_eq!(1, misses);
232    }
233
234    #[test]
235    fn clear() {
236        let mut c = UnboundCache::new();
237
238        assert_eq!(c.cache_set(1, 100), None);
239        assert_eq!(c.cache_set(2, 200), None);
240        assert_eq!(c.cache_set(3, 300), None);
241
242        // register some hits and misses
243        c.cache_get(&1);
244        c.cache_get(&2);
245        c.cache_get(&3);
246        c.cache_get(&10);
247        c.cache_get(&20);
248        c.cache_get(&30);
249
250        assert_eq!(3, c.cache_size());
251        assert_eq!(3, c.cache_hits().unwrap());
252        assert_eq!(3, c.cache_misses().unwrap());
253        assert!(3 <= c.store.capacity());
254
255        // clear the cache, should have no more elements
256        // hits and misses will still be kept
257        c.cache_clear();
258
259        assert_eq!(0, c.cache_size());
260        assert_eq!(3, c.cache_hits().unwrap());
261        assert_eq!(3, c.cache_misses().unwrap());
262        assert!(3 <= c.store.capacity()); // Keeps the allocated memory for reuse.
263
264        let capacity = 1;
265        let mut c = UnboundCache::with_capacity(capacity);
266        assert!(capacity <= c.store.capacity());
267
268        assert_eq!(c.cache_set(1, 100), None);
269        assert_eq!(c.cache_set(2, 200), None);
270        assert_eq!(c.cache_set(3, 300), None);
271
272        assert!(3 <= c.store.capacity());
273
274        c.cache_clear();
275
276        assert!(3 <= c.store.capacity()); // Keeps the allocated memory for reuse.
277    }
278
279    #[test]
280    fn reset() {
281        let mut c = UnboundCache::new();
282        assert_eq!(c.cache_set(1, 100), None);
283        assert_eq!(c.cache_set(2, 200), None);
284        assert_eq!(c.cache_set(3, 300), None);
285        assert!(3 <= c.store.capacity());
286
287        c.cache_reset();
288
289        assert_eq!(0, c.store.capacity());
290
291        let init_capacity = 1;
292        let mut c = UnboundCache::with_capacity(init_capacity);
293        assert_eq!(c.cache_set(1, 100), None);
294        assert_eq!(c.cache_set(2, 200), None);
295        assert_eq!(c.cache_set(3, 300), None);
296        assert!(3 <= c.store.capacity());
297
298        c.cache_reset();
299
300        assert!(init_capacity <= c.store.capacity());
301    }
302
303    #[test]
304    fn remove() {
305        let mut c = UnboundCache::new();
306
307        assert_eq!(c.cache_set(1, 100), None);
308        assert_eq!(c.cache_set(2, 200), None);
309        assert_eq!(c.cache_set(3, 300), None);
310
311        // register some hits and misses
312        c.cache_get(&1);
313        c.cache_get(&2);
314        c.cache_get(&3);
315        c.cache_get(&10);
316        c.cache_get(&20);
317        c.cache_get(&30);
318
319        assert_eq!(3, c.cache_size());
320        assert_eq!(3, c.cache_hits().unwrap());
321        assert_eq!(3, c.cache_misses().unwrap());
322
323        // remove some items from cache
324        // hits and misses will still be kept
325        assert_eq!(Some(100), c.cache_remove(&1));
326
327        assert_eq!(2, c.cache_size());
328        assert_eq!(3, c.cache_hits().unwrap());
329        assert_eq!(3, c.cache_misses().unwrap());
330
331        assert_eq!(Some(200), c.cache_remove(&2));
332
333        assert_eq!(1, c.cache_size());
334
335        // removing extra is ok
336        assert_eq!(None, c.cache_remove(&2));
337
338        assert_eq!(1, c.cache_size());
339    }
340
341    #[test]
342    fn get_or_set_with() {
343        let mut c = UnboundCache::new();
344
345        assert_eq!(c.cache_get_or_set_with(0, || 0), &0);
346        assert_eq!(c.cache_get_or_set_with(1, || 1), &1);
347        assert_eq!(c.cache_get_or_set_with(2, || 2), &2);
348        assert_eq!(c.cache_get_or_set_with(3, || 3), &3);
349        assert_eq!(c.cache_get_or_set_with(4, || 4), &4);
350        assert_eq!(c.cache_get_or_set_with(5, || 5), &5);
351
352        assert_eq!(c.cache_misses(), Some(6));
353
354        assert_eq!(c.cache_get_or_set_with(0, || 0), &0);
355
356        assert_eq!(c.cache_misses(), Some(6));
357
358        assert_eq!(c.cache_get_or_set_with(0, || 42), &0);
359
360        assert_eq!(c.cache_misses(), Some(6));
361
362        assert_eq!(c.cache_get_or_set_with(1, || 1), &1);
363
364        assert_eq!(c.cache_misses(), Some(6));
365
366        c.cache_reset();
367        fn _try_get(n: usize) -> Result<usize, String> {
368            if n < 10 {
369                Ok(n)
370            } else {
371                Err("dead".to_string())
372            }
373        }
374        let res: Result<&mut usize, String> = c.cache_try_get_or_set_with(0, || _try_get(10));
375        assert!(res.is_err());
376
377        let res: Result<&mut usize, String> = c.cache_try_get_or_set_with(0, || _try_get(1));
378        assert_eq!(res.unwrap(), &1);
379        let res: Result<&mut usize, String> = c.cache_try_get_or_set_with(0, || _try_get(5));
380        assert_eq!(res.unwrap(), &1);
381    }
382}