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}