common/
cache.rs

1//! This file contains an interface to a static DashMap, such that the stored values can be extracted with static lifetimes,
2//! and without a Ref Guard.
3//!
4//! The underlying implementation stores all values within an Arc. When a value is requested, the interior Arc's address is
5//! returned directly. This is safe, as:
6//!
7//! 1.  The static DashMap holds a strong reference to the Arc, so its lifetime is static. There is no risk of a reference
8//!     becoming invalid.
9//! 2.  Because the DashMap stores an Arc, the interior address of the value is outside the realm of the Map. Therefore,
10//!     insertions and deletions will not alter the value within the Arc, even if it modifies the address of the Arc
11//!     itself.cache
12//! 3.  Typical Rust thread-safety and reference rules still apply. You can take as many immutable references as you want,
13//!     but only a single mutable reference. This means, if you need to query the data across multiple threads, you'll
14//!     need to wrap the data in a Mutex or similar interior-mutable structure.
15//!
16//! To use this, you will need to define two static values:
17//!
18//! 1.  The DashMap itself. Define a new static cache::CacheStatic with the Key and Value types needed.
19//! 2.  Instantiate a static of the cache::Cache object within a LazyLock, with its value taking a reference
20//!     to the above DashMap.
21//!
22//! Then, you can use the Cache to retrieve static values, and insert them.
23
24use dashmap::{DashMap, mapref::one::Ref};
25use std::{
26    borrow::Borrow,
27    hash::Hash,
28    sync::{Arc, LazyLock},
29};
30
31/// The underlying data store. You should not use this besides passing it to the Cache instance.
32pub type CacheStatic<K, V> = LazyLock<DashMap<K, Arc<V>, ahash::RandomState>>;
33
34/// The Cache interface. Store within a LazyLock, and pass the corresponding CacheStatic to the LazyLock::new()
35pub struct Cache<K: Eq + Hash + 'static, V: 'static> {
36    container: &'static CacheStatic<K, V>,
37}
38impl<K: Eq + Hash + Clone + 'static, V: 'static> Cache<K, V> {
39    /// Extract the value from within the Arc.
40    /// This function is safe so long as the Arc is held by a static container
41    fn get_static(arc: Ref<K, Arc<V>>) -> &'static V {
42        // Get the arc from within the reference, bumping the reference count by one.
43        let arc = arc.value().clone();
44
45        // Consume the arc to get the raw pointer, decreasing the reference back down.
46        let raw_ptr: *const V = Arc::into_raw(arc);
47
48        // Because the Arc is contained in a static container, the lifetime of the interior
49        // value is likewise static, so this is safe.
50        //
51        // Also note that because we are storing in an Arc, we don't need to worry about the
52        // DashMap changing the address from underneath us. It stores the Arc, not the
53        // ptr we're returning here.
54        let static_ref: &'static V = unsafe { &*raw_ptr };
55        static_ref
56    }
57
58    /// Construct a new Cache. This should be provided to the LazyLock holding this object.
59    ///
60    /// ## Example
61    ///
62    /// ```rust
63    /// use common::cache::{CacheStatic, Cache};
64    /// use dashmap::DashMap;
65    /// use std::{
66    ///     borrow::Cow,
67    ///     sync::LazyLock
68    /// };
69    ///
70    /// static CACHE: CacheStatic<String, Cow<'static, str>> = LazyLock::new(DashMap::default);
71    /// pub static MY_CACHE: LazyLock<Cache<String, Cow<'static, str>>> = LazyLock::new(|| Cache::new(&CACHE));
72    /// ```
73    pub fn new(container: &'static CacheStatic<K, V>) -> Self {
74        Self { container }
75    }
76
77    /// Get a value from within the Cache, if it exists.
78    ///
79    /// ## Example
80    ///
81    /// ```rust
82    /// use common::cache::{CacheStatic, Cache};
83    /// use dashmap::DashMap;
84    /// use std::{
85    ///     borrow::Cow,
86    ///     sync::LazyLock
87    /// };
88    ///
89    /// static CACHE: CacheStatic<String, Cow<'static, str>> = LazyLock::new(DashMap::default);
90    /// pub static MY_CACHE: LazyLock<Cache<String, Cow<'static, str>>> = LazyLock::new(|| Cache::new(&CACHE));
91    ///
92    /// MY_CACHE.insert("Test".to_string(), Cow::Borrowed("Another"));
93    /// let value: &'static str = MY_CACHE.get("Test").unwrap();
94    /// assert!(value == "Another");
95    /// ```
96    pub fn get<R>(&self, key: &R) -> Option<&'static V>
97    where
98        R: ?Sized + Hash + Eq,
99        K: Borrow<R>,
100    {
101        if let Some(arc) = self.container.get(key) {
102            Some(Self::get_static(arc))
103        } else {
104            None
105        }
106    }
107
108    /// Insert a new value into the Cache, returning the stored value.
109    ///
110    /// ## Example
111    ///
112    /// ```rust
113    /// use common::cache::{CacheStatic, Cache};
114    /// use dashmap::DashMap;
115    /// use std::{
116    ///     borrow::Cow,
117    ///     sync::LazyLock
118    /// };
119    ///
120    /// static CACHE: CacheStatic<String, Cow<'static, str>> = LazyLock::new(DashMap::default);
121    /// pub static MY_CACHE: LazyLock<Cache<String, Cow<'static, str>>> = LazyLock::new(|| Cache::new(&CACHE));
122    ///
123    /// let interior: &'static str = MY_CACHE.insert("Test".to_string(), Cow::Borrowed("Another"));
124    /// assert!(interior == "Another");
125    /// ```
126    pub fn insert(&self, key: K, value: V) -> &'static V {
127        self.container.insert(key.clone(), Arc::new(value));
128        self.get(&key).unwrap()
129    }
130}