quick_cache/
unsync.rs

1use crate::{
2    linked_slab::Token,
3    options::*,
4    shard::{self, CacheShard, InsertStrategy},
5    DefaultHashBuilder, Equivalent, Lifecycle, UnitWeighter, Weighter,
6};
7use std::hash::{BuildHasher, Hash};
8
9/// A non-concurrent cache.
10#[derive(Clone)]
11pub struct Cache<
12    Key,
13    Val,
14    We = UnitWeighter,
15    B = DefaultHashBuilder,
16    L = DefaultLifecycle<Key, Val>,
17> {
18    shard: CacheShard<Key, Val, We, B, L, SharedPlaceholder>,
19}
20
21impl<Key: Eq + Hash, Val> Cache<Key, Val> {
22    /// Creates a new cache with holds up to `items_capacity` items (approximately).
23    pub fn new(items_capacity: usize) -> Self {
24        Self::with(
25            items_capacity,
26            items_capacity as u64,
27            Default::default(),
28            Default::default(),
29            Default::default(),
30        )
31    }
32}
33
34impl<Key: Eq + Hash, Val, We: Weighter<Key, Val>> Cache<Key, Val, We> {
35    pub fn with_weighter(
36        estimated_items_capacity: usize,
37        weight_capacity: u64,
38        weighter: We,
39    ) -> Self {
40        Self::with(
41            estimated_items_capacity,
42            weight_capacity,
43            weighter,
44            Default::default(),
45            Default::default(),
46        )
47    }
48}
49
50impl<Key: Eq + Hash, Val, We: Weighter<Key, Val>, B: BuildHasher, L: Lifecycle<Key, Val>>
51    Cache<Key, Val, We, B, L>
52{
53    /// Creates a new cache that can hold up to `weight_capacity` in weight.
54    /// `estimated_items_capacity` is the estimated number of items the cache is expected to hold,
55    /// roughly equivalent to `weight_capacity / average item weight`.
56    pub fn with(
57        estimated_items_capacity: usize,
58        weight_capacity: u64,
59        weighter: We,
60        hash_builder: B,
61        lifecycle: L,
62    ) -> Self {
63        Self::with_options(
64            OptionsBuilder::new()
65                .estimated_items_capacity(estimated_items_capacity)
66                .weight_capacity(weight_capacity)
67                .build()
68                .unwrap(),
69            weighter,
70            hash_builder,
71            lifecycle,
72        )
73    }
74
75    /// Constructs a cache based on [OptionsBuilder].
76    ///
77    /// # Example
78    ///
79    /// ```rust
80    /// use quick_cache::{unsync::{Cache, DefaultLifecycle}, OptionsBuilder, UnitWeighter, DefaultHashBuilder};
81    ///
82    /// Cache::<(String, u64), String>::with_options(
83    ///   OptionsBuilder::new()
84    ///     .estimated_items_capacity(10000)
85    ///     .weight_capacity(10000)
86    ///     .build()
87    ///     .unwrap(),
88    ///     UnitWeighter,
89    ///     DefaultHashBuilder::default(),
90    ///     DefaultLifecycle::default(),
91    /// );
92    /// ```
93    pub fn with_options(options: Options, weighter: We, hash_builder: B, lifecycle: L) -> Self {
94        let shard = CacheShard::new(
95            options.hot_allocation,
96            options.ghost_allocation,
97            options.estimated_items_capacity,
98            options.weight_capacity,
99            weighter,
100            hash_builder,
101            lifecycle,
102        );
103        Self { shard }
104    }
105
106    /// Returns whether the cache is empty.
107    pub fn is_empty(&self) -> bool {
108        self.shard.len() == 0
109    }
110
111    /// Returns the number of cached items
112    pub fn len(&self) -> usize {
113        self.shard.len()
114    }
115
116    /// Returns the total weight of cached items
117    pub fn weight(&self) -> u64 {
118        self.shard.weight()
119    }
120
121    /// Returns the maximum weight of cached items
122    pub fn capacity(&self) -> u64 {
123        self.shard.capacity()
124    }
125
126    /// Returns the number of misses
127    #[cfg(feature = "stats")]
128    pub fn misses(&self) -> u64 {
129        self.shard.misses()
130    }
131
132    /// Returns the number of hits
133    #[cfg(feature = "stats")]
134    pub fn hits(&self) -> u64 {
135        self.shard.hits()
136    }
137
138    /// Reserver additional space for `additional` entries.
139    /// Note that this is counted in entries, and is not weighted.
140    pub fn reserve(&mut self, additional: usize) {
141        self.shard.reserve(additional);
142    }
143
144    /// Check if a key exist in the cache.
145    pub fn contains_key<Q>(&self, key: &Q) -> bool
146    where
147        Q: Hash + Equivalent<Key> + ?Sized,
148    {
149        self.shard.contains(self.shard.hash(key), key)
150    }
151
152    /// Fetches an item from the cache.
153    pub fn get<Q>(&self, key: &Q) -> Option<&Val>
154    where
155        Q: Hash + Equivalent<Key> + ?Sized,
156    {
157        self.shard.get(self.shard.hash(key), key)
158    }
159
160    /// Fetches an item from the cache.
161    ///
162    /// Note: Leaking the returned RefMut might cause undefined behavior.
163    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<RefMut<'_, Key, Val, We, B, L>>
164    where
165        Q: Hash + Equivalent<Key> + ?Sized,
166    {
167        self.shard.get_mut(self.shard.hash(key), key).map(RefMut)
168    }
169
170    /// Peeks an item from the cache. Contrary to gets, peeks don't alter the key "hotness".
171    pub fn peek<Q>(&self, key: &Q) -> Option<&Val>
172    where
173        Q: Hash + Equivalent<Key> + ?Sized,
174    {
175        self.shard.peek(self.shard.hash(key), key)
176    }
177
178    /// Peeks an item from the cache. Contrary to gets, peeks don't alter the key "hotness".
179    ///
180    /// Note: Leaking the returned RefMut might cause undefined behavior.
181    pub fn peek_mut<Q>(&mut self, key: &Q) -> Option<RefMut<'_, Key, Val, We, B, L>>
182    where
183        Q: Hash + Equivalent<Key> + ?Sized,
184    {
185        self.shard.peek_mut(self.shard.hash(key), key).map(RefMut)
186    }
187
188    /// Remove an item from the cache whose key is `key`.
189    /// Returns the removed entry, if any.
190    pub fn remove<Q>(&mut self, key: &Q) -> Option<(Key, Val)>
191    where
192        Q: Hash + Equivalent<Key> + ?Sized,
193    {
194        self.shard.remove(self.shard.hash(key), key)
195    }
196
197    /// Replaces an item in the cache, but only if it already exists.
198    /// If `soft` is set, the replace operation won't affect the "hotness" of the key,
199    /// even if the value is replaced.
200    ///
201    /// Returns `Ok` if the entry was admitted and `Err(_)` if it wasn't.
202    pub fn replace(&mut self, key: Key, value: Val, soft: bool) -> Result<(), (Key, Val)> {
203        let lcs = self.replace_with_lifecycle(key, value, soft)?;
204        self.shard.lifecycle.end_request(lcs);
205        Ok(())
206    }
207
208    /// Replaces an item in the cache, but only if it already exists.
209    /// If `soft` is set, the replace operation won't affect the "hotness" of the key,
210    /// even if the value is replaced.
211    ///
212    /// Returns `Ok` if the entry was admitted and `Err(_)` if it wasn't.
213    pub fn replace_with_lifecycle(
214        &mut self,
215        key: Key,
216        value: Val,
217        soft: bool,
218    ) -> Result<L::RequestState, (Key, Val)> {
219        let mut lcs = self.shard.lifecycle.begin_request();
220        self.shard.insert(
221            &mut lcs,
222            self.shard.hash(&key),
223            key,
224            value,
225            InsertStrategy::Replace { soft },
226        )?;
227        Ok(lcs)
228    }
229
230    /// Retains only the items specified by the predicate.
231    /// In other words, remove all items for which `f(&key, &value)` returns `false`. The
232    /// elements are visited in arbitrary order.
233    pub fn retain<F>(&mut self, f: F)
234    where
235        F: Fn(&Key, &Val) -> bool,
236    {
237        self.shard.retain(f);
238    }
239
240    /// Gets or inserts an item in the cache with key `key`.
241    /// Returns a reference to the inserted `value` if it was admitted to the cache.
242    ///
243    /// See also `get_ref_or_guard`.
244    pub fn get_or_insert_with<Q, E>(
245        &mut self,
246        key: &Q,
247        with: impl FnOnce() -> Result<Val, E>,
248    ) -> Result<Option<&Val>, E>
249    where
250        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
251    {
252        let idx = match self.shard.upsert_placeholder(self.shard.hash(key), key) {
253            Ok((idx, _)) => idx,
254            Err((plh, _)) => {
255                let v = with()?;
256                let mut lcs = self.shard.lifecycle.begin_request();
257                let replaced = self.shard.replace_placeholder(&mut lcs, &plh, false, v);
258                self.shard.lifecycle.end_request(lcs);
259                debug_assert!(replaced.is_ok(), "unsync replace_placeholder can't fail");
260                plh.idx
261            }
262        };
263        Ok(self.shard.peek_token(idx))
264    }
265
266    /// Gets or inserts an item in the cache with key `key`.
267    /// Returns a mutable reference to the inserted `value` if it was admitted to the cache.
268    ///
269    /// See also `get_mut_or_guard`.
270    pub fn get_mut_or_insert_with<'a, Q, E>(
271        &'a mut self,
272        key: &Q,
273        with: impl FnOnce() -> Result<Val, E>,
274    ) -> Result<Option<RefMut<'a, Key, Val, We, B, L>>, E>
275    where
276        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
277    {
278        let idx = match self.shard.upsert_placeholder(self.shard.hash(key), key) {
279            Ok((idx, _)) => idx,
280            Err((plh, _)) => {
281                let v = with()?;
282                let mut lcs = self.shard.lifecycle.begin_request();
283                let replaced = self.shard.replace_placeholder(&mut lcs, &plh, false, v);
284                debug_assert!(replaced.is_ok(), "unsync replace_placeholder can't fail");
285                self.shard.lifecycle.end_request(lcs);
286                plh.idx
287            }
288        };
289        Ok(self.shard.peek_token_mut(idx).map(RefMut))
290    }
291
292    /// Gets an item from the cache with key `key` .
293    /// If the corresponding value isn't present in the cache, this functions returns a guard
294    /// that can be used to insert the value once it's computed.
295    pub fn get_ref_or_guard<Q>(&mut self, key: &Q) -> Result<&Val, Guard<'_, Key, Val, We, B, L>>
296    where
297        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
298    {
299        // TODO: this could be using a simpler entry API
300        match self.shard.upsert_placeholder(self.shard.hash(key), key) {
301            Ok((_, v)) => unsafe {
302                // Rustc gets insanely confused about returning from mut borrows
303                // Safety: v has the same lifetime as self
304                let v: *const Val = v;
305                Ok(&*v)
306            },
307            Err((placeholder, _)) => Err(Guard {
308                cache: self,
309                placeholder,
310                inserted: false,
311            }),
312        }
313    }
314
315    /// Gets an item from the cache with key `key` .
316    /// If the corresponding value isn't present in the cache, this functions returns a guard
317    /// that can be used to insert the value once it's computed.
318    pub fn get_mut_or_guard<'a, Q>(
319        &'a mut self,
320        key: &Q,
321    ) -> Result<Option<RefMut<'a, Key, Val, We, B, L>>, Guard<'a, Key, Val, We, B, L>>
322    where
323        Q: Hash + Equivalent<Key> + ToOwned<Owned = Key> + ?Sized,
324    {
325        // TODO: this could be using a simpler entry API
326        match self.shard.upsert_placeholder(self.shard.hash(key), key) {
327            Ok((idx, _)) => Ok(self.shard.peek_token_mut(idx).map(RefMut)),
328            Err((placeholder, _)) => Err(Guard {
329                cache: self,
330                placeholder,
331                inserted: false,
332            }),
333        }
334    }
335
336    /// Inserts an item in the cache with key `key`.
337    pub fn insert(&mut self, key: Key, value: Val) {
338        let lcs = self.insert_with_lifecycle(key, value);
339        self.shard.lifecycle.end_request(lcs);
340    }
341
342    /// Inserts an item in the cache with key `key`.
343    pub fn insert_with_lifecycle(&mut self, key: Key, value: Val) -> L::RequestState {
344        let mut lcs = self.shard.lifecycle.begin_request();
345        let result = self.shard.insert(
346            &mut lcs,
347            self.shard.hash(&key),
348            key,
349            value,
350            InsertStrategy::Insert,
351        );
352        // result cannot err with the Insert strategy
353        debug_assert!(result.is_ok());
354        lcs
355    }
356
357    /// Clear all items from the cache
358    pub fn clear(&mut self) {
359        self.shard.clear();
360    }
361
362    /// Iterator for the items in the cache
363    pub fn iter(&self) -> impl Iterator<Item = (&'_ Key, &'_ Val)> + '_ {
364        // TODO: add a concrete type, impl trait in the public api is really bad.
365        self.shard.iter()
366    }
367
368    /// Drain all items from the cache
369    ///
370    /// The cache will be emptied even if the returned iterator isn't fully consumed.
371    pub fn drain(&mut self) -> impl Iterator<Item = (Key, Val)> + '_ {
372        // TODO: add a concrete type, impl trait in the public api is really bad.
373        self.shard.drain()
374    }
375
376    #[cfg(any(fuzzing, test))]
377    pub fn validate(&self, accept_overweight: bool) {
378        self.shard.validate(accept_overweight);
379    }
380}
381
382impl<Key, Val, We, B, L> std::fmt::Debug for Cache<Key, Val, We, B, L> {
383    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384        f.debug_struct("Cache").finish_non_exhaustive()
385    }
386}
387
388/// Default `Lifecycle` for the unsync cache.
389pub struct DefaultLifecycle<Key, Val>(std::marker::PhantomData<(Key, Val)>);
390
391impl<Key, Val> std::fmt::Debug for DefaultLifecycle<Key, Val> {
392    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393        f.debug_tuple("DefaultLifecycle").finish()
394    }
395}
396
397impl<Key, Val> Default for DefaultLifecycle<Key, Val> {
398    #[inline]
399    fn default() -> Self {
400        Self(Default::default())
401    }
402}
403
404impl<Key, Val> Clone for DefaultLifecycle<Key, Val> {
405    #[inline]
406    fn clone(&self) -> Self {
407        Self(Default::default())
408    }
409}
410
411impl<Key, Val> Lifecycle<Key, Val> for DefaultLifecycle<Key, Val> {
412    type RequestState = ();
413
414    #[inline]
415    fn begin_request(&self) -> Self::RequestState {}
416
417    #[inline]
418    fn on_evict(&self, _state: &mut Self::RequestState, _key: Key, _val: Val) {}
419}
420
421#[derive(Debug, Clone)]
422struct SharedPlaceholder {
423    hash: u64,
424    idx: Token,
425}
426
427pub struct Guard<'a, Key, Val, We, B, L> {
428    cache: &'a mut Cache<Key, Val, We, B, L>,
429    placeholder: SharedPlaceholder,
430    inserted: bool,
431}
432
433impl<Key: Eq + Hash, Val, We: Weighter<Key, Val>, B: BuildHasher, L: Lifecycle<Key, Val>>
434    Guard<'_, Key, Val, We, B, L>
435{
436    /// Inserts the value into the placeholder
437    pub fn insert(self, value: Val) {
438        self.insert_internal(value, false);
439    }
440
441    /// Inserts the value into the placeholder
442    pub fn insert_with_lifecycle(self, value: Val) -> L::RequestState {
443        self.insert_internal(value, true).unwrap()
444    }
445
446    #[inline]
447    fn insert_internal(mut self, value: Val, return_lcs: bool) -> Option<L::RequestState> {
448        let mut lcs = self.cache.shard.lifecycle.begin_request();
449        let replaced =
450            self.cache
451                .shard
452                .replace_placeholder(&mut lcs, &self.placeholder, false, value);
453        debug_assert!(replaced.is_ok(), "unsync replace_placeholder can't fail");
454        self.inserted = true;
455        if return_lcs {
456            Some(lcs)
457        } else {
458            self.cache.shard.lifecycle.end_request(lcs);
459            None
460        }
461    }
462}
463
464impl<Key, Val, We, B, L> Drop for Guard<'_, Key, Val, We, B, L> {
465    #[inline]
466    fn drop(&mut self) {
467        #[cold]
468        fn drop_slow<Key, Val, We, B, L>(this: &mut Guard<'_, Key, Val, We, B, L>) {
469            this.cache.shard.remove_placeholder(&this.placeholder);
470        }
471        if !self.inserted {
472            drop_slow(self);
473        }
474    }
475}
476
477pub struct RefMut<'cache, Key, Val, We: Weighter<Key, Val>, B, L>(
478    crate::shard::RefMut<'cache, Key, Val, We, B, L, SharedPlaceholder>,
479);
480
481impl<Key, Val, We: Weighter<Key, Val>, B, L> std::ops::Deref for RefMut<'_, Key, Val, We, B, L> {
482    type Target = Val;
483
484    #[inline]
485    fn deref(&self) -> &Self::Target {
486        self.0.pair().1
487    }
488}
489
490impl<Key, Val, We: Weighter<Key, Val>, B, L> std::ops::DerefMut for RefMut<'_, Key, Val, We, B, L> {
491    #[inline]
492    fn deref_mut(&mut self) -> &mut Self::Target {
493        self.0.value_mut()
494    }
495}
496
497impl shard::SharedPlaceholder for SharedPlaceholder {
498    #[inline]
499    fn new(hash: u64, idx: Token) -> Self {
500        Self { hash, idx }
501    }
502
503    #[inline]
504    fn same_as(&self, _other: &Self) -> bool {
505        true
506    }
507
508    #[inline]
509    fn hash(&self) -> u64 {
510        self.hash
511    }
512
513    #[inline]
514    fn idx(&self) -> Token {
515        self.idx
516    }
517}
518
519#[cfg(test)]
520mod tests {
521    use super::*;
522
523    struct Weighter;
524
525    impl crate::Weighter<u32, u32> for Weighter {
526        fn weight(&self, _key: &u32, val: &u32) -> u64 {
527            *val as u64
528        }
529    }
530
531    #[test]
532    fn test_zero_weights() {
533        let mut cache = Cache::with_weighter(100, 100, Weighter);
534        cache.insert(0, 0);
535        assert_eq!(cache.weight(), 0);
536        for i in 1..100 {
537            cache.insert(i, i);
538            cache.insert(i, i);
539        }
540        assert_eq!(cache.get(&0).copied(), Some(0));
541        assert!(cache.contains_key(&0));
542        let a = cache.weight();
543        *cache.get_mut(&0).unwrap() += 1;
544        assert_eq!(cache.weight(), a + 1);
545        for i in 1..100 {
546            cache.insert(i, i);
547            cache.insert(i, i);
548        }
549        assert_eq!(cache.get(&0), None);
550        assert!(!cache.contains_key(&0));
551
552        cache.insert(0, 1);
553        let a = cache.weight();
554        *cache.get_mut(&0).unwrap() -= 1;
555        assert_eq!(cache.weight(), a - 1);
556        for i in 1..100 {
557            cache.insert(i, i);
558            cache.insert(i, i);
559        }
560        assert_eq!(cache.get(&0).copied(), Some(0));
561        assert!(cache.contains_key(&0));
562    }
563}