Skip to main content

fixed_cache/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![cfg_attr(not(feature = "std"), no_std)]
4#![allow(clippy::new_without_default)]
5
6#[cfg(feature = "alloc")]
7extern crate alloc;
8
9use core::{
10    cell::UnsafeCell,
11    convert::Infallible,
12    fmt,
13    hash::{BuildHasher, Hash},
14    marker::PhantomData,
15    mem::{self, MaybeUninit},
16    ptr,
17    sync::atomic::{AtomicUsize, Ordering},
18};
19use equivalent::Equivalent;
20
21#[cfg(feature = "stats")]
22mod stats;
23#[cfg(feature = "stats")]
24#[cfg_attr(docsrs, doc(cfg(feature = "stats")))]
25pub use stats::{AnyRef, CountingStatsHandler, Stats, StatsHandler};
26
27const LOCKED_BIT: usize = 1 << 0;
28const ALIVE_BIT: usize = 1 << 1;
29const NEEDED_BITS: usize = 2;
30
31const EPOCH_BITS: usize = 10;
32const EPOCH_SHIFT: usize = NEEDED_BITS;
33const EPOCH_MASK: usize = ((1 << EPOCH_BITS) - 1) << EPOCH_SHIFT;
34const EPOCH_NEEDED_BITS: usize = NEEDED_BITS + EPOCH_BITS;
35
36#[cfg(feature = "rapidhash")]
37type DefaultBuildHasher = core::hash::BuildHasherDefault<rapidhash::fast::RapidHasher<'static>>;
38#[cfg(all(not(feature = "rapidhash"), feature = "std"))]
39type DefaultBuildHasher = std::hash::RandomState;
40#[cfg(all(not(feature = "rapidhash"), not(feature = "std")))]
41type DefaultBuildHasher = core::hash::BuildHasherDefault<NoDefaultHasher>;
42
43#[cfg(all(not(feature = "rapidhash"), not(feature = "std")))]
44#[doc(hidden)]
45pub enum NoDefaultHasher {}
46
47/// Configuration trait for [`Cache`].
48///
49/// This trait allows customizing cache behavior through compile-time configuration.
50pub trait CacheConfig {
51    /// Whether to track statistics for cache performance.
52    ///
53    /// When enabled, the cache tracks hit and miss counts for each key, allowing
54    /// monitoring of cache performance.
55    ///
56    /// Only enabled when the `stats` feature is also enabled.
57    ///
58    /// Defaults to `true`.
59    const STATS: bool = true;
60
61    /// Whether to track epochs for cheap invalidation via [`Cache::clear`].
62    ///
63    /// When enabled, the cache tracks an epoch counter that is incremented on each call to
64    /// [`Cache::clear`]. Entries are considered invalid if their epoch doesn't match the current
65    /// epoch, allowing O(1) invalidation of all entries without touching each bucket.
66    ///
67    /// Defaults to `false`.
68    const EPOCHS: bool = false;
69}
70
71/// Default cache configuration.
72pub struct DefaultCacheConfig(());
73impl CacheConfig for DefaultCacheConfig {}
74
75/// A concurrent, fixed-size, set-associative cache.
76///
77/// This cache maps keys to values using a fixed number of buckets. Each key hashes to exactly
78/// one bucket, and collisions are resolved by eviction (the new value replaces the old one).
79///
80/// # Thread Safety
81///
82/// The cache is safe to share across threads (`Send + Sync`). All operations use atomic
83/// instructions and never block, making it suitable for high-contention scenarios.
84///
85/// # Limitations
86///
87/// - **Eviction on collision**: When two keys hash to the same bucket, the older entry is evicted.
88/// - **No iteration**: Individual entries cannot be enumerated.
89///
90/// # Type Parameters
91///
92/// - `K`: The key type. Must implement [`Hash`] + [`Eq`].
93/// - `V`: The value type. Must implement [`Clone`].
94/// - `S`: The hash builder type. Must implement [`BuildHasher`]. Defaults to [`RandomState`] or
95///   [`rapidhash`] if the `rapidhash` feature is enabled.
96///
97/// # Example
98///
99/// ```
100/// use fixed_cache::Cache;
101///
102/// let cache: Cache<u64, u64> = Cache::new(256, Default::default());
103///
104/// // Insert a value
105/// cache.insert(42, 100);
106/// assert_eq!(cache.get(&42), Some(100));
107///
108/// // Get or compute a value
109/// let value = cache.get_or_insert_with(123, |&k| k * 2);
110/// assert_eq!(value, 246);
111/// ```
112///
113/// [`Hash`]: std::hash::Hash
114/// [`Eq`]: std::cmp::Eq
115/// [`Clone`]: std::clone::Clone
116/// [`Drop`]: std::ops::Drop
117/// [`BuildHasher`]: std::hash::BuildHasher
118/// [`RandomState`]: std::hash::RandomState
119/// [`rapidhash`]: https://crates.io/crates/rapidhash
120pub struct Cache<K, V, S = DefaultBuildHasher, C: CacheConfig = DefaultCacheConfig> {
121    entries: *const [Bucket<(K, V)>],
122    build_hasher: S,
123    #[cfg(feature = "stats")]
124    stats: Option<Stats<K, V>>,
125    #[cfg(feature = "alloc")]
126    drop: bool,
127    epoch: AtomicUsize,
128    _config: PhantomData<C>,
129}
130
131impl<K, V, S, C: CacheConfig> fmt::Debug for Cache<K, V, S, C> {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        f.debug_struct("Cache").finish_non_exhaustive()
134    }
135}
136
137// SAFETY: `Cache` is safe to share across threads because `Bucket` uses atomic operations.
138unsafe impl<K: Send, V: Send, S: Send, C: CacheConfig + Send> Send for Cache<K, V, S, C> {}
139unsafe impl<K: Send, V: Send, S: Sync, C: CacheConfig + Sync> Sync for Cache<K, V, S, C> {}
140
141impl<K, V, S, C> Cache<K, V, S, C>
142where
143    K: Hash + Eq,
144    S: BuildHasher,
145    C: CacheConfig,
146{
147    const NEEDS_DROP: bool = Bucket::<(K, V)>::NEEDS_DROP;
148
149    /// Create a new cache with the specified number of entries and hasher.
150    ///
151    /// Dynamically allocates memory for the cache entries.
152    ///
153    /// # Panics
154    ///
155    /// Panics if `num`:
156    /// - is not a power of two.
157    /// - isn't at least 4.
158    // See len_assertion for why.
159    #[cfg(feature = "alloc")]
160    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
161    pub fn new(num: usize, build_hasher: S) -> Self {
162        Self::len_assertion(num);
163        let layout = alloc::alloc::Layout::array::<Bucket<(K, V)>>(num).unwrap();
164        let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
165        if ptr.is_null() {
166            alloc::alloc::handle_alloc_error(layout);
167        }
168        let entries = ptr::slice_from_raw_parts(ptr.cast::<Bucket<(K, V)>>(), num);
169        Self::new_inner(entries, build_hasher, true)
170    }
171
172    /// Creates a new cache with the specified entries and hasher.
173    ///
174    /// # Panics
175    ///
176    /// See [`new`](Self::new).
177    #[inline]
178    pub const fn new_static(entries: &'static [Bucket<(K, V)>], build_hasher: S) -> Self {
179        Self::len_assertion(entries.len());
180        Self::new_inner(entries, build_hasher, false)
181    }
182
183    /// Sets the cache's statistics.
184    ///
185    /// Can only be called when [`CacheConfig::STATS`] is `true`.
186    #[cfg(feature = "stats")]
187    #[inline]
188    pub fn with_stats(mut self, stats: Option<Stats<K, V>>) -> Self {
189        self.set_stats(stats);
190        self
191    }
192
193    /// Sets the cache's statistics.
194    ///
195    /// Can only be called when [`CacheConfig::STATS`] is `true`.
196    #[cfg(feature = "stats")]
197    #[inline]
198    pub fn set_stats(&mut self, stats: Option<Stats<K, V>>) {
199        const { assert!(C::STATS, "can only set stats when C::STATS is true") }
200        self.stats = stats;
201    }
202
203    #[inline]
204    const fn new_inner(entries: *const [Bucket<(K, V)>], build_hasher: S, drop: bool) -> Self {
205        let _ = drop;
206        Self {
207            entries,
208            build_hasher,
209            #[cfg(feature = "stats")]
210            stats: None,
211            #[cfg(feature = "alloc")]
212            drop,
213            epoch: AtomicUsize::new(0),
214            _config: PhantomData,
215        }
216    }
217
218    #[inline]
219    const fn len_assertion(len: usize) {
220        // We need `RESERVED_BITS` bits to store metadata for each entry.
221        // Since we calculate the tag mask based on the index mask, and the index mask is (len - 1),
222        // we assert that the length's bottom `RESERVED_BITS` bits are zero.
223        let reserved = if C::EPOCHS { EPOCH_NEEDED_BITS } else { NEEDED_BITS };
224        assert!(len.is_power_of_two(), "length must be a power of two");
225        assert!((len & ((1 << reserved) - 1)) == 0, "len must have its bottom N bits set to zero");
226    }
227
228    #[inline]
229    const fn index_mask(&self) -> usize {
230        let n = self.capacity();
231        unsafe { core::hint::assert_unchecked(n.is_power_of_two()) };
232        n - 1
233    }
234
235    #[inline]
236    const fn tag_mask(&self) -> usize {
237        !self.index_mask()
238    }
239
240    /// Returns the current epoch of the cache.
241    ///
242    /// Only meaningful when [`CacheConfig::EPOCHS`] is `true`.
243    #[inline]
244    fn epoch(&self) -> usize {
245        self.epoch.load(Ordering::Relaxed)
246    }
247
248    /// Clears the cache by invalidating all entries.
249    ///
250    /// This is O(1): it simply increments an epoch counter. On epoch wraparound, falls back to
251    /// [`clear_slow`](Self::clear_slow).
252    ///
253    /// Can only be called when [`CacheConfig::EPOCHS`] is `true`.
254    #[inline]
255    pub fn clear(&self) {
256        const EPOCH_MAX: usize = (1 << EPOCH_BITS) - 1;
257        const { assert!(C::EPOCHS, "can only .clear() when C::EPOCHS is true") }
258        let prev = self.epoch.fetch_add(1, Ordering::Release);
259        // Tags store only the low EPOCH_BITS of the epoch, clear the cache on every low-bit wrap.
260        if (prev & EPOCH_MAX) == EPOCH_MAX {
261            self.clear_slow();
262        }
263    }
264
265    /// Clears the cache by zeroing all bucket memory.
266    ///
267    /// This is O(N) where N is the number of buckets. Prefer [`clear`](Self::clear) when
268    /// [`CacheConfig::EPOCHS`] is `true`.
269    ///
270    /// # Safety
271    ///
272    /// This method is safe but may race with concurrent operations. Callers should ensure
273    /// no other threads are accessing the cache during this operation.
274    pub fn clear_slow(&self) {
275        // SAFETY: We zero the entire bucket array. This is safe because:
276        // - Bucket<T> is repr(C) and zeroed memory is a valid empty bucket state.
277        // - We don't need to drop existing entries since we're zeroing the ALIVE_BIT.
278        // - Concurrent readers will see either the old state or zeros (empty).
279        unsafe {
280            ptr::write_bytes(
281                self.entries.cast_mut().cast::<Bucket<(K, V)>>(),
282                0,
283                self.entries.len(),
284            )
285        };
286    }
287
288    /// Returns the hash builder used by this cache.
289    #[inline]
290    pub const fn hasher(&self) -> &S {
291        &self.build_hasher
292    }
293
294    /// Returns the number of entries in this cache.
295    #[inline]
296    pub const fn capacity(&self) -> usize {
297        self.entries.len()
298    }
299
300    /// Returns the statistics of this cache.
301    #[cfg(feature = "stats")]
302    pub const fn stats(&self) -> Option<&Stats<K, V>> {
303        self.stats.as_ref()
304    }
305}
306
307impl<K, V, S, C> Cache<K, V, S, C>
308where
309    K: Hash + Eq,
310    V: Clone,
311    S: BuildHasher,
312    C: CacheConfig,
313{
314    /// Get an entry from the cache.
315    pub fn get<Q: ?Sized + Hash + Equivalent<K>>(&self, key: &Q) -> Option<V> {
316        let (bucket, tag) = self.calc(key);
317        self.get_inner(key, bucket, tag)
318    }
319
320    #[inline]
321    fn get_inner<Q: ?Sized + Hash + Equivalent<K>>(
322        &self,
323        key: &Q,
324        bucket: &Bucket<(K, V)>,
325        tag: usize,
326    ) -> Option<V> {
327        if bucket.try_lock(Some(tag)) {
328            // SAFETY: We hold the lock and bucket is alive, so we have exclusive access.
329            let (ck, v) = unsafe { (*bucket.data.get()).assume_init_ref() };
330            if key.equivalent(ck) {
331                #[cfg(feature = "stats")]
332                if C::STATS
333                    && let Some(stats) = &self.stats
334                {
335                    stats.record_hit(ck, v);
336                }
337                let v = v.clone();
338                bucket.unlock(tag);
339                return Some(v);
340            }
341            bucket.unlock(tag);
342        }
343        #[cfg(feature = "stats")]
344        if C::STATS
345            && let Some(stats) = &self.stats
346        {
347            stats.record_miss(AnyRef::new(&key));
348        }
349        None
350    }
351
352    /// Insert an entry into the cache.
353    pub fn insert(&self, key: K, value: V) {
354        let (bucket, tag) = self.calc(&key);
355        self.insert_inner(bucket, tag, || (key, value));
356    }
357
358    /// Remove an entry from the cache.
359    ///
360    /// Returns the value if the key was present in the cache.
361    pub fn remove<Q: ?Sized + Hash + Equivalent<K>>(&self, key: &Q) -> Option<V> {
362        let (bucket, tag) = self.calc(key);
363        if bucket.try_lock(Some(tag)) {
364            // SAFETY: We hold the lock and bucket is alive, so we have exclusive access.
365            let data = unsafe { &mut *bucket.data.get() };
366            let (ck, v) = unsafe { data.assume_init_ref() };
367            if key.equivalent(ck) {
368                let v = v.clone();
369                #[cfg(feature = "stats")]
370                if C::STATS
371                    && let Some(stats) = &self.stats
372                {
373                    stats.record_remove(ck, &v);
374                }
375                if Self::NEEDS_DROP {
376                    // SAFETY: We hold the lock, so we have exclusive access.
377                    unsafe { data.assume_init_drop() };
378                }
379                bucket.unlock(0);
380                return Some(v);
381            }
382            bucket.unlock(tag);
383        }
384        None
385    }
386
387    #[inline]
388    fn insert_inner(
389        &self,
390        bucket: &Bucket<(K, V)>,
391        tag: usize,
392        make_entry: impl FnOnce() -> (K, V),
393    ) {
394        #[inline(always)]
395        unsafe fn do_write<T>(ptr: *mut T, f: impl FnOnce() -> T) {
396            // This function is translated as:
397            // - allocate space for a T on the stack
398            // - call f() with the return value being put onto this stack space
399            // - memcpy from the stack to the heap
400            //
401            // Ideally we want LLVM to always realize that doing a stack
402            // allocation is unnecessary and optimize the code so it writes
403            // directly into the heap instead. It seems we get it to realize
404            // this most consistently if we put this critical line into it's
405            // own function instead of inlining it into the surrounding code.
406            unsafe { ptr::write(ptr, f()) };
407        }
408
409        let Some(prev_tag) = bucket.try_lock_ret(None) else {
410            return;
411        };
412
413        // SAFETY: We hold the lock, so we have exclusive access.
414        unsafe {
415            let is_alive = (prev_tag & !LOCKED_BIT) != 0;
416            let data = bucket.data.get().cast::<(K, V)>();
417
418            if C::STATS && cfg!(feature = "stats") {
419                #[cfg(feature = "stats")]
420                if is_alive {
421                    let (old_key, old_value) = ptr::replace(data, make_entry());
422                    if let Some(stats) = &self.stats {
423                        stats.record_insert(&(*data).0, &(*data).1, Some((&old_key, &old_value)));
424                    }
425                } else {
426                    do_write(data, make_entry);
427                    if let Some(stats) = &self.stats {
428                        stats.record_insert(&(*data).0, &(*data).1, None);
429                    }
430                }
431            } else {
432                if Self::NEEDS_DROP && is_alive {
433                    ptr::drop_in_place(data);
434                }
435                do_write(data, make_entry);
436            }
437        }
438        bucket.unlock(tag);
439    }
440
441    /// Gets a value from the cache, or inserts one computed by `f` if not present.
442    ///
443    /// If the key is found in the cache, returns a clone of the cached value.
444    /// Otherwise, calls `f` to compute the value, attempts to insert it, and returns it.
445    #[inline]
446    pub fn get_or_insert_with<F>(&self, key: K, f: F) -> V
447    where
448        F: FnOnce(&K) -> V,
449    {
450        let Ok(v) = self.get_or_try_insert_with(key, |key| Ok::<_, Infallible>(f(key)));
451        v
452    }
453
454    /// Gets a value from the cache, or inserts one computed by `f` if not present.
455    ///
456    /// If the key is found in the cache, returns a clone of the cached value.
457    /// Otherwise, calls `f` to compute the value, attempts to insert it, and returns it.
458    ///
459    /// This is the same as [`get_or_insert_with`], but takes a reference to the key, and a function
460    /// to get the key reference to an owned key.
461    ///
462    /// [`get_or_insert_with`]: Self::get_or_insert_with
463    #[inline]
464    pub fn get_or_insert_with_ref<'a, Q, F, Cvt>(&self, key: &'a Q, f: F, cvt: Cvt) -> V
465    where
466        Q: ?Sized + Hash + Equivalent<K>,
467        F: FnOnce(&'a Q) -> V,
468        Cvt: FnOnce(&'a Q) -> K,
469    {
470        let Ok(v) = self.get_or_try_insert_with_ref(key, |key| Ok::<_, Infallible>(f(key)), cvt);
471        v
472    }
473
474    /// Gets a value from the cache, or attempts to insert one computed by `f` if not present.
475    ///
476    /// If the key is found in the cache, returns `Ok` with a clone of the cached value.
477    /// Otherwise, calls `f` to compute the value. If `f` returns `Ok`, attempts to insert
478    /// the value and returns it. If `f` returns `Err`, the error is propagated.
479    #[inline]
480    pub fn get_or_try_insert_with<F, E>(&self, key: K, f: F) -> Result<V, E>
481    where
482        F: FnOnce(&K) -> Result<V, E>,
483    {
484        let mut key = mem::ManuallyDrop::new(key);
485        let mut read = false;
486        let r = self.get_or_try_insert_with_ref(&*key, f, |k| {
487            read = true;
488            unsafe { ptr::read(k) }
489        });
490        if !read {
491            unsafe { mem::ManuallyDrop::drop(&mut key) }
492        }
493        r
494    }
495
496    /// Gets a value from the cache, or attempts to insert one computed by `f` if not present.
497    ///
498    /// If the key is found in the cache, returns `Ok` with a clone of the cached value.
499    /// Otherwise, calls `f` to compute the value. If `f` returns `Ok`, attempts to insert
500    /// the value and returns it. If `f` returns `Err`, the error is propagated.
501    ///
502    /// This is the same as [`Self::get_or_try_insert_with`], but takes a reference to the key, and
503    /// a function to get the key reference to an owned key.
504    #[inline]
505    pub fn get_or_try_insert_with_ref<'a, Q, F, Cvt, E>(
506        &self,
507        key: &'a Q,
508        f: F,
509        cvt: Cvt,
510    ) -> Result<V, E>
511    where
512        Q: ?Sized + Hash + Equivalent<K>,
513        F: FnOnce(&'a Q) -> Result<V, E>,
514        Cvt: FnOnce(&'a Q) -> K,
515    {
516        let (bucket, tag) = self.calc(key);
517        if let Some(v) = self.get_inner(key, bucket, tag) {
518            return Ok(v);
519        }
520        let value = f(key)?;
521        self.insert_inner(bucket, tag, || (cvt(key), value.clone()));
522        Ok(value)
523    }
524
525    #[inline]
526    fn calc<Q: ?Sized + Hash + Equivalent<K>>(&self, key: &Q) -> (&Bucket<(K, V)>, usize) {
527        let hash = self.hash_key(key);
528        // SAFETY: index is masked to be within bounds.
529        let bucket = unsafe { (&*self.entries).get_unchecked(hash & self.index_mask()) };
530        let mut tag = hash & self.tag_mask();
531        if Self::NEEDS_DROP {
532            tag |= ALIVE_BIT;
533        }
534        if C::EPOCHS {
535            tag = (tag & !EPOCH_MASK) | ((self.epoch() << EPOCH_SHIFT) & EPOCH_MASK);
536        }
537        (bucket, tag)
538    }
539
540    #[inline]
541    fn hash_key<Q: ?Sized + Hash + Equivalent<K>>(&self, key: &Q) -> usize {
542        let hash = self.build_hasher.hash_one(key);
543
544        if cfg!(target_pointer_width = "32") {
545            ((hash >> 32) as usize) ^ (hash as usize)
546        } else {
547            hash as usize
548        }
549    }
550}
551
552impl<K, V, S, C: CacheConfig> Drop for Cache<K, V, S, C> {
553    fn drop(&mut self) {
554        #[cfg(feature = "alloc")]
555        if self.drop {
556            // SAFETY: `Drop` has exclusive access.
557            drop(unsafe { alloc::boxed::Box::from_raw(self.entries.cast_mut()) });
558        }
559    }
560}
561
562/// A single cache bucket that holds one key-value pair.
563///
564/// Buckets are aligned to 128 bytes to avoid false sharing between cache lines.
565/// Each bucket contains an atomic tag for lock-free synchronization and uninitialized
566/// storage for the data.
567///
568/// This type is public to allow use with the [`static_cache!`] macro for compile-time
569/// cache initialization. You typically don't need to interact with it directly.
570#[repr(C, align(128))]
571#[doc(hidden)]
572pub struct Bucket<T> {
573    tag: AtomicUsize,
574    data: UnsafeCell<MaybeUninit<T>>,
575}
576
577impl<T> Bucket<T> {
578    const NEEDS_DROP: bool = mem::needs_drop::<T>();
579
580    /// Creates a new zeroed bucket.
581    #[inline]
582    pub const fn new() -> Self {
583        Self { tag: AtomicUsize::new(0), data: UnsafeCell::new(MaybeUninit::zeroed()) }
584    }
585
586    #[inline]
587    fn try_lock(&self, expected: Option<usize>) -> bool {
588        self.try_lock_ret(expected).is_some()
589    }
590
591    #[inline]
592    fn try_lock_ret(&self, expected: Option<usize>) -> Option<usize> {
593        let state = self.tag.load(Ordering::Relaxed);
594        if let Some(expected) = expected {
595            if state != expected {
596                return None;
597            }
598        } else if state & LOCKED_BIT != 0 {
599            return None;
600        }
601        self.tag
602            .compare_exchange(state, state | LOCKED_BIT, Ordering::Acquire, Ordering::Relaxed)
603            .ok()
604    }
605
606    #[inline]
607    fn is_alive(&self) -> bool {
608        self.tag.load(Ordering::Relaxed) & ALIVE_BIT != 0
609    }
610
611    #[inline]
612    fn unlock(&self, tag: usize) {
613        self.tag.store(tag, Ordering::Release);
614    }
615}
616
617// SAFETY: `Bucket` is a specialized `Mutex<T>` that never blocks.
618unsafe impl<T: Send> Send for Bucket<T> {}
619unsafe impl<T: Send> Sync for Bucket<T> {}
620
621impl<T> Drop for Bucket<T> {
622    fn drop(&mut self) {
623        if Self::NEEDS_DROP && self.is_alive() {
624            // SAFETY: `Drop` has exclusive access.
625            unsafe { self.data.get_mut().assume_init_drop() };
626        }
627    }
628}
629
630/// Declares a static cache with the given name, key type, value type, and size.
631///
632/// The size must be a power of two.
633///
634/// # Example
635///
636/// ```
637/// # #[cfg(feature = "rapidhash")] {
638/// use fixed_cache::{Cache, static_cache};
639///
640/// type BuildHasher = std::hash::BuildHasherDefault<rapidhash::fast::RapidHasher<'static>>;
641///
642/// static MY_CACHE: Cache<u64, &'static str, BuildHasher> =
643///     static_cache!(u64, &'static str, 1024, BuildHasher::new());
644///
645/// let value = MY_CACHE.get_or_insert_with(42, |_k| "hi");
646/// assert_eq!(value, "hi");
647///
648/// let new_value = MY_CACHE.get_or_insert_with(42, |_k| "not hi");
649/// assert_eq!(new_value, "hi");
650/// # }
651/// ```
652#[macro_export]
653macro_rules! static_cache {
654    ($K:ty, $V:ty, $size:expr) => {
655        $crate::static_cache!($K, $V, $size, Default::default())
656    };
657    ($K:ty, $V:ty, $size:expr, $hasher:expr) => {{
658        static ENTRIES: [$crate::Bucket<($K, $V)>; $size] =
659            [const { $crate::Bucket::new() }; $size];
660        $crate::Cache::new_static(&ENTRIES, $hasher)
661    }};
662}
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667    use std::thread;
668
669    const fn iters(n: usize) -> usize {
670        if cfg!(miri) { n / 10 } else { n }
671    }
672
673    type BH = std::hash::BuildHasherDefault<rapidhash::fast::RapidHasher<'static>>;
674    type Cache<K, V> = super::Cache<K, V, BH>;
675
676    struct EpochConfig;
677    impl CacheConfig for EpochConfig {
678        const EPOCHS: bool = true;
679    }
680    type EpochCache<K, V> = super::Cache<K, V, BH, EpochConfig>;
681
682    struct NoStatsConfig;
683    impl CacheConfig for NoStatsConfig {
684        const STATS: bool = false;
685    }
686    type NoStatsCache<K, V> = super::Cache<K, V, BH, NoStatsConfig>;
687
688    fn new_cache<K: Hash + Eq, V: Clone>(size: usize) -> Cache<K, V> {
689        Cache::new(size, Default::default())
690    }
691
692    #[test]
693    fn basic_get_or_insert() {
694        let cache = new_cache(1024);
695
696        let mut computed = false;
697        let value = cache.get_or_insert_with(42, |&k| {
698            computed = true;
699            k * 2
700        });
701        assert!(computed);
702        assert_eq!(value, 84);
703
704        computed = false;
705        let value = cache.get_or_insert_with(42, |&k| {
706            computed = true;
707            k * 2
708        });
709        assert!(!computed);
710        assert_eq!(value, 84);
711    }
712
713    #[test]
714    fn different_keys() {
715        let cache: Cache<String, usize> = new_cache(1024);
716
717        let v1 = cache.get_or_insert_with("hello".to_string(), |s| s.len());
718        let v2 = cache.get_or_insert_with("world!".to_string(), |s| s.len());
719
720        assert_eq!(v1, 5);
721        assert_eq!(v2, 6);
722    }
723
724    #[test]
725    fn new_dynamic_allocation() {
726        let cache: Cache<u32, u32> = new_cache(64);
727        assert_eq!(cache.capacity(), 64);
728
729        cache.insert(1, 100);
730        assert_eq!(cache.get(&1), Some(100));
731    }
732
733    #[test]
734    fn get_miss() {
735        let cache = new_cache::<u64, u64>(64);
736        assert_eq!(cache.get(&999), None);
737    }
738
739    #[test]
740    fn insert_and_get() {
741        let cache: Cache<u64, String> = new_cache(64);
742
743        cache.insert(1, "one".to_string());
744        cache.insert(2, "two".to_string());
745        cache.insert(3, "three".to_string());
746
747        assert_eq!(cache.get(&1), Some("one".to_string()));
748        assert_eq!(cache.get(&2), Some("two".to_string()));
749        assert_eq!(cache.get(&3), Some("three".to_string()));
750        assert_eq!(cache.get(&4), None);
751    }
752
753    #[test]
754    fn insert_twice() {
755        let cache = new_cache(64);
756
757        cache.insert(42, 1);
758        assert_eq!(cache.get(&42), Some(1));
759
760        cache.insert(42, 2);
761        let v = cache.get(&42);
762        assert!(v == Some(1) || v == Some(2));
763    }
764
765    #[test]
766    fn remove_existing() {
767        let cache: Cache<u64, String> = new_cache(64);
768
769        cache.insert(1, "one".to_string());
770        assert_eq!(cache.get(&1), Some("one".to_string()));
771
772        let removed = cache.remove(&1);
773        assert_eq!(removed, Some("one".to_string()));
774        assert_eq!(cache.get(&1), None);
775    }
776
777    #[test]
778    fn remove_nonexistent() {
779        let cache = new_cache::<u64, u64>(64);
780        assert_eq!(cache.remove(&999), None);
781    }
782
783    #[test]
784    fn get_or_insert_with_ref() {
785        let cache: Cache<String, usize> = new_cache(64);
786
787        let key = "hello";
788        let value = cache.get_or_insert_with_ref(key, |s| s.len(), |s| s.to_string());
789        assert_eq!(value, 5);
790
791        let value2 = cache.get_or_insert_with_ref(key, |_| 999, |s| s.to_string());
792        assert_eq!(value2, 5);
793    }
794
795    #[test]
796    fn get_or_insert_with_ref_different_keys() {
797        let cache: Cache<String, usize> = new_cache(1024);
798
799        let v1 = cache.get_or_insert_with_ref("foo", |s| s.len(), |s| s.to_string());
800        let v2 = cache.get_or_insert_with_ref("barbaz", |s| s.len(), |s| s.to_string());
801
802        assert_eq!(v1, 3);
803        assert_eq!(v2, 6);
804    }
805
806    #[test]
807    fn capacity() {
808        let cache = new_cache::<u64, u64>(256);
809        assert_eq!(cache.capacity(), 256);
810
811        let cache2 = new_cache::<u64, u64>(128);
812        assert_eq!(cache2.capacity(), 128);
813    }
814
815    #[test]
816    fn hasher() {
817        let cache = new_cache::<u64, u64>(64);
818        let _ = cache.hasher();
819    }
820
821    #[test]
822    fn debug_impl() {
823        let cache = new_cache::<u64, u64>(64);
824        let debug_str = format!("{:?}", cache);
825        assert!(debug_str.contains("Cache"));
826    }
827
828    #[test]
829    fn bucket_new() {
830        let bucket: Bucket<(u64, u64)> = Bucket::new();
831        assert_eq!(bucket.tag.load(Ordering::Relaxed), 0);
832    }
833
834    #[test]
835    fn many_entries() {
836        let cache: Cache<u64, u64> = new_cache(1024);
837        let n = iters(500);
838
839        for i in 0..n as u64 {
840            cache.insert(i, i * 2);
841        }
842
843        let mut hits = 0;
844        for i in 0..n as u64 {
845            if cache.get(&i) == Some(i * 2) {
846                hits += 1;
847            }
848        }
849        assert!(hits > 0);
850    }
851
852    #[test]
853    fn string_keys() {
854        let cache: Cache<String, i32> = new_cache(1024);
855
856        cache.insert("alpha".to_string(), 1);
857        cache.insert("beta".to_string(), 2);
858        cache.insert("gamma".to_string(), 3);
859
860        assert_eq!(cache.get("alpha"), Some(1));
861        assert_eq!(cache.get("beta"), Some(2));
862        assert_eq!(cache.get("gamma"), Some(3));
863    }
864
865    #[test]
866    fn zero_values() {
867        let cache: Cache<u64, u64> = new_cache(64);
868
869        cache.insert(0, 0);
870        assert_eq!(cache.get(&0), Some(0));
871
872        cache.insert(1, 0);
873        assert_eq!(cache.get(&1), Some(0));
874    }
875
876    #[test]
877    fn clone_value() {
878        #[derive(Clone, PartialEq, Debug)]
879        struct MyValue(u64);
880
881        let cache: Cache<u64, MyValue> = new_cache(64);
882
883        cache.insert(1, MyValue(123));
884        let v = cache.get(&1);
885        assert_eq!(v, Some(MyValue(123)));
886    }
887
888    fn run_concurrent<F>(num_threads: usize, f: F)
889    where
890        F: Fn(usize) + Send + Sync,
891    {
892        thread::scope(|s| {
893            for t in 0..num_threads {
894                let f = &f;
895                s.spawn(move || f(t));
896            }
897        });
898    }
899
900    #[test]
901    fn concurrent_reads() {
902        let cache: Cache<u64, u64> = new_cache(1024);
903        let n = iters(100);
904
905        for i in 0..n as u64 {
906            cache.insert(i, i * 10);
907        }
908
909        run_concurrent(4, |_| {
910            for i in 0..n as u64 {
911                let _ = cache.get(&i);
912            }
913        });
914    }
915
916    #[test]
917    fn concurrent_writes() {
918        let cache: Cache<u64, u64> = new_cache(1024);
919        let n = iters(100);
920
921        run_concurrent(4, |t| {
922            for i in 0..n {
923                cache.insert((t * 1000 + i) as u64, i as u64);
924            }
925        });
926    }
927
928    #[test]
929    fn concurrent_read_write() {
930        let cache: Cache<u64, u64> = new_cache(256);
931        let n = iters(1000);
932
933        run_concurrent(2, |t| {
934            for i in 0..n as u64 {
935                if t == 0 {
936                    cache.insert(i % 100, i);
937                } else {
938                    let _ = cache.get(&(i % 100));
939                }
940            }
941        });
942    }
943
944    #[test]
945    fn concurrent_get_or_insert() {
946        let cache: Cache<u64, u64> = new_cache(1024);
947        let n = iters(100);
948
949        run_concurrent(8, |_| {
950            for i in 0..n as u64 {
951                let _ = cache.get_or_insert_with(i, |&k| k * 2);
952            }
953        });
954
955        for i in 0..n as u64 {
956            if let Some(v) = cache.get(&i) {
957                assert_eq!(v, i * 2);
958            }
959        }
960    }
961
962    #[test]
963    #[should_panic = "power of two"]
964    fn non_power_of_two() {
965        let _ = new_cache::<u64, u64>(100);
966    }
967
968    #[test]
969    #[should_panic = "len must have its bottom N bits set to zero"]
970    fn small_cache() {
971        let _ = new_cache::<u64, u64>(2);
972    }
973
974    #[test]
975    fn power_of_two_sizes() {
976        for shift in 2..10 {
977            let size = 1 << shift;
978            let cache = new_cache::<u64, u64>(size);
979            assert_eq!(cache.capacity(), size);
980        }
981    }
982
983    #[test]
984    fn equivalent_key_lookup() {
985        let cache: Cache<String, i32> = new_cache(64);
986
987        cache.insert("hello".to_string(), 42);
988
989        assert_eq!(cache.get("hello"), Some(42));
990    }
991
992    #[test]
993    fn large_values() {
994        let cache: Cache<u64, [u8; 1000]> = new_cache(64);
995
996        let large_value = [42u8; 1000];
997        cache.insert(1, large_value);
998
999        assert_eq!(cache.get(&1), Some(large_value));
1000    }
1001
1002    #[test]
1003    fn send_sync() {
1004        fn assert_send<T: Send>() {}
1005        fn assert_sync<T: Sync>() {}
1006
1007        assert_send::<Cache<u64, u64>>();
1008        assert_sync::<Cache<u64, u64>>();
1009        assert_send::<Bucket<(u64, u64)>>();
1010        assert_sync::<Bucket<(u64, u64)>>();
1011    }
1012
1013    #[test]
1014    fn get_or_try_insert_with_ok() {
1015        let cache = new_cache(1024);
1016
1017        let mut computed = false;
1018        let result: Result<u64, &str> = cache.get_or_try_insert_with(42, |&k| {
1019            computed = true;
1020            Ok(k * 2)
1021        });
1022        assert!(computed);
1023        assert_eq!(result, Ok(84));
1024
1025        computed = false;
1026        let result: Result<u64, &str> = cache.get_or_try_insert_with(42, |&k| {
1027            computed = true;
1028            Ok(k * 2)
1029        });
1030        assert!(!computed);
1031        assert_eq!(result, Ok(84));
1032    }
1033
1034    #[test]
1035    fn get_or_try_insert_with_err() {
1036        let cache: Cache<u64, u64> = new_cache(1024);
1037
1038        let result: Result<u64, &str> = cache.get_or_try_insert_with(42, |_| Err("failed"));
1039        assert_eq!(result, Err("failed"));
1040
1041        assert_eq!(cache.get(&42), None);
1042    }
1043
1044    #[test]
1045    fn get_or_try_insert_with_ref_ok() {
1046        let cache: Cache<String, usize> = new_cache(64);
1047
1048        let key = "hello";
1049        let result: Result<usize, &str> =
1050            cache.get_or_try_insert_with_ref(key, |s| Ok(s.len()), |s| s.to_string());
1051        assert_eq!(result, Ok(5));
1052
1053        let result2: Result<usize, &str> =
1054            cache.get_or_try_insert_with_ref(key, |_| Ok(999), |s| s.to_string());
1055        assert_eq!(result2, Ok(5));
1056    }
1057
1058    #[test]
1059    fn get_or_try_insert_with_ref_err() {
1060        let cache: Cache<String, usize> = new_cache(64);
1061
1062        let key = "hello";
1063        let result: Result<usize, &str> =
1064            cache.get_or_try_insert_with_ref(key, |_| Err("failed"), |s| s.to_string());
1065        assert_eq!(result, Err("failed"));
1066
1067        assert_eq!(cache.get(key), None);
1068    }
1069
1070    #[test]
1071    fn drop_on_cache_drop() {
1072        use std::sync::atomic::{AtomicUsize, Ordering};
1073
1074        static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
1075
1076        #[derive(Clone, Hash, Eq, PartialEq)]
1077        struct DropKey(u64);
1078        impl Drop for DropKey {
1079            fn drop(&mut self) {
1080                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
1081            }
1082        }
1083
1084        #[derive(Clone)]
1085        struct DropValue(#[allow(dead_code)] u64);
1086        impl Drop for DropValue {
1087            fn drop(&mut self) {
1088                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
1089            }
1090        }
1091
1092        DROP_COUNT.store(0, Ordering::SeqCst);
1093        {
1094            let cache: super::Cache<DropKey, DropValue, BH> =
1095                super::Cache::new(64, Default::default());
1096            cache.insert(DropKey(1), DropValue(100));
1097            cache.insert(DropKey(2), DropValue(200));
1098            cache.insert(DropKey(3), DropValue(300));
1099            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
1100        }
1101        // 3 keys + 3 values = 6 drops
1102        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 6);
1103    }
1104
1105    #[test]
1106    fn drop_on_eviction() {
1107        use std::sync::atomic::{AtomicUsize, Ordering};
1108
1109        static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
1110
1111        #[derive(Clone, Hash, Eq, PartialEq)]
1112        struct DropKey(u64);
1113        impl Drop for DropKey {
1114            fn drop(&mut self) {
1115                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
1116            }
1117        }
1118
1119        #[derive(Clone)]
1120        struct DropValue(#[allow(dead_code)] u64);
1121        impl Drop for DropValue {
1122            fn drop(&mut self) {
1123                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
1124            }
1125        }
1126
1127        DROP_COUNT.store(0, Ordering::SeqCst);
1128        {
1129            let cache: super::Cache<DropKey, DropValue, BH> =
1130                super::Cache::new(64, Default::default());
1131            cache.insert(DropKey(1), DropValue(100));
1132            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
1133            // Insert same key again - should evict old entry
1134            cache.insert(DropKey(1), DropValue(200));
1135            // Old key + old value dropped = 2
1136            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
1137        }
1138        // Cache dropped: new key + new value = 2 more
1139        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 4);
1140    }
1141
1142    #[test]
1143    fn epoch_clear() {
1144        let cache: EpochCache<u64, u64> = EpochCache::new(4096, Default::default());
1145
1146        assert_eq!(cache.epoch(), 0);
1147
1148        cache.insert(1, 100);
1149        cache.insert(2, 200);
1150        assert_eq!(cache.get(&1), Some(100));
1151        assert_eq!(cache.get(&2), Some(200));
1152
1153        cache.clear();
1154        assert_eq!(cache.epoch(), 1);
1155
1156        assert_eq!(cache.get(&1), None);
1157        assert_eq!(cache.get(&2), None);
1158
1159        cache.insert(1, 101);
1160        assert_eq!(cache.get(&1), Some(101));
1161
1162        cache.clear();
1163        assert_eq!(cache.epoch(), 2);
1164        assert_eq!(cache.get(&1), None);
1165    }
1166
1167    #[test]
1168    fn epoch_wrap_around() {
1169        let cache: EpochCache<u64, u64> = EpochCache::new(4096, Default::default());
1170
1171        for _ in 0..300 {
1172            cache.insert(42, 123);
1173            assert_eq!(cache.get(&42), Some(123));
1174            cache.clear();
1175            assert_eq!(cache.get(&42), None);
1176        }
1177    }
1178
1179    #[test]
1180    fn no_stats_config() {
1181        let cache: NoStatsCache<u64, u64> = NoStatsCache::new(64, Default::default());
1182
1183        cache.insert(1, 100);
1184        assert_eq!(cache.get(&1), Some(100));
1185        assert_eq!(cache.get(&999), None);
1186
1187        cache.insert(1, 200);
1188        assert_eq!(cache.get(&1), Some(200));
1189
1190        cache.remove(&1);
1191        assert_eq!(cache.get(&1), None);
1192
1193        let v = cache.get_or_insert_with(42, |&k| k * 2);
1194        assert_eq!(v, 84);
1195    }
1196
1197    #[test]
1198    fn epoch_wraparound_stays_cleared() {
1199        let cache: EpochCache<u64, u64> = EpochCache::new(4096, Default::default());
1200
1201        cache.insert(42, 123);
1202        assert_eq!(cache.get(&42), Some(123));
1203
1204        for i in 0..2048 {
1205            cache.clear();
1206            assert_eq!(cache.get(&42), None, "failed at clear #{i}");
1207        }
1208    }
1209
1210    #[test]
1211    fn epoch_repeated_wraparound_stays_cleared() {
1212        let cache: EpochCache<u64, u64> = EpochCache::new(4096, Default::default());
1213
1214        for _ in 0..1024 {
1215            cache.clear();
1216        }
1217
1218        cache.insert(42, 123);
1219        assert_eq!(cache.get(&42), Some(123));
1220
1221        for i in 0..1024 {
1222            cache.clear();
1223            assert_eq!(cache.get(&42), None, "failed at clear #{i}");
1224        }
1225    }
1226
1227    #[test]
1228    fn remove_copy_type() {
1229        let cache = new_cache::<u64, u64>(64);
1230
1231        cache.insert(1, 100);
1232        assert_eq!(cache.get(&1), Some(100));
1233
1234        let removed = cache.remove(&1);
1235        assert_eq!(removed, Some(100));
1236        assert_eq!(cache.get(&1), None);
1237
1238        cache.insert(1, 200);
1239        assert_eq!(cache.get(&1), Some(200));
1240    }
1241
1242    #[test]
1243    fn remove_then_reinsert_copy() {
1244        let cache = new_cache::<u64, u64>(64);
1245
1246        for i in 0..100u64 {
1247            cache.insert(1, i);
1248            assert_eq!(cache.get(&1), Some(i));
1249            assert_eq!(cache.remove(&1), Some(i));
1250            assert_eq!(cache.get(&1), None);
1251        }
1252    }
1253
1254    #[test]
1255    fn epoch_with_needs_drop() {
1256        let cache: EpochCache<String, String> = EpochCache::new(4096, Default::default());
1257
1258        cache.insert("key".to_string(), "value".to_string());
1259        assert_eq!(cache.get("key"), Some("value".to_string()));
1260
1261        cache.clear();
1262        assert_eq!(cache.get("key"), None);
1263
1264        cache.insert("key".to_string(), "value2".to_string());
1265        assert_eq!(cache.get("key"), Some("value2".to_string()));
1266    }
1267
1268    #[test]
1269    fn epoch_remove() {
1270        let cache: EpochCache<u64, u64> = EpochCache::new(4096, Default::default());
1271
1272        cache.insert(1, 100);
1273        assert_eq!(cache.remove(&1), Some(100));
1274        assert_eq!(cache.get(&1), None);
1275
1276        cache.insert(1, 200);
1277        assert_eq!(cache.get(&1), Some(200));
1278
1279        cache.clear();
1280        assert_eq!(cache.get(&1), None);
1281        assert_eq!(cache.remove(&1), None);
1282    }
1283
1284    #[test]
1285    fn no_stats_needs_drop() {
1286        let cache: NoStatsCache<String, String> = NoStatsCache::new(64, Default::default());
1287
1288        cache.insert("a".to_string(), "b".to_string());
1289        assert_eq!(cache.get("a"), Some("b".to_string()));
1290
1291        cache.insert("a".to_string(), "c".to_string());
1292        assert_eq!(cache.get("a"), Some("c".to_string()));
1293
1294        cache.remove(&"a".to_string());
1295        assert_eq!(cache.get("a"), None);
1296    }
1297
1298    #[test]
1299    fn no_stats_get_or_insert() {
1300        let cache: NoStatsCache<String, usize> = NoStatsCache::new(64, Default::default());
1301
1302        let v = cache.get_or_insert_with_ref("hello", |s| s.len(), |s| s.to_string());
1303        assert_eq!(v, 5);
1304
1305        let v2 = cache.get_or_insert_with_ref("hello", |_| 999, |s| s.to_string());
1306        assert_eq!(v2, 5);
1307    }
1308
1309    #[test]
1310    fn epoch_concurrent() {
1311        let cache: EpochCache<u64, u64> = EpochCache::new(4096, Default::default());
1312        let n = iters(10_000);
1313
1314        run_concurrent(4, |t| {
1315            for i in 0..n as u64 {
1316                match t {
1317                    0 => {
1318                        cache.insert(i % 50, i);
1319                    }
1320                    1 => {
1321                        let _ = cache.get(&(i % 50));
1322                    }
1323                    2 => {
1324                        if i % 100 == 0 {
1325                            cache.clear();
1326                        }
1327                    }
1328                    _ => {
1329                        let _ = cache.remove(&(i % 50));
1330                    }
1331                }
1332            }
1333        });
1334    }
1335}