mini_moka/sync/
builder.rs

1use super::Cache;
2use crate::{common::builder_utils, common::concurrent::Weigher};
3
4use std::{
5    collections::hash_map::RandomState,
6    hash::{BuildHasher, Hash},
7    marker::PhantomData,
8    sync::Arc,
9    time::Duration,
10};
11
12/// Builds a [`Cache`][cache-struct] or with various configuration knobs.
13///
14/// [cache-struct]: ./struct.Cache.html
15///
16/// # Examples
17///
18/// ```rust
19/// use mini_moka::sync::Cache;
20/// use std::time::Duration;
21///
22/// let cache = Cache::builder()
23///     // Max 10,000 entries
24///     .max_capacity(10_000)
25///     // Time to live (TTL): 30 minutes
26///     .time_to_live(Duration::from_secs(30 * 60))
27///     // Time to idle (TTI):  5 minutes
28///     .time_to_idle(Duration::from_secs( 5 * 60))
29///     // Create the cache.
30///     .build();
31///
32/// // This entry will expire after 5 minutes (TTI) if there is no get().
33/// cache.insert(0, "zero");
34///
35/// // This get() will extend the entry life for another 5 minutes.
36/// cache.get(&0);
37///
38/// // Even though we keep calling get(), the entry will expire
39/// // after 30 minutes (TTL) from the insert().
40/// ```
41///
42#[must_use]
43pub struct CacheBuilder<K, V, C> {
44    max_capacity: Option<u64>,
45    initial_capacity: Option<usize>,
46    weigher: Option<Weigher<K, V>>,
47    time_to_live: Option<Duration>,
48    time_to_idle: Option<Duration>,
49    cache_type: PhantomData<C>,
50}
51
52impl<K, V> Default for CacheBuilder<K, V, Cache<K, V, RandomState>>
53where
54    K: Eq + Hash + Send + Sync + 'static,
55    V: Clone + Send + Sync + 'static,
56{
57    fn default() -> Self {
58        Self {
59            max_capacity: None,
60            initial_capacity: None,
61            weigher: None,
62            time_to_live: None,
63            time_to_idle: None,
64            cache_type: Default::default(),
65        }
66    }
67}
68
69impl<K, V> CacheBuilder<K, V, Cache<K, V, RandomState>>
70where
71    K: Eq + Hash + Send + Sync + 'static,
72    V: Clone + Send + Sync + 'static,
73{
74    /// Construct a new `CacheBuilder` that will be used to build a `Cache` or
75    /// `SegmentedCache` holding up to `max_capacity` entries.
76    pub fn new(max_capacity: u64) -> Self {
77        Self {
78            max_capacity: Some(max_capacity),
79            ..Default::default()
80        }
81    }
82
83    /// Builds a `Cache<K, V>`.
84    ///
85    /// If you want to build a `SegmentedCache<K, V>`, call `segments` method before
86    /// calling this method.
87    ///
88    /// # Panics
89    ///
90    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
91    /// 1000 years. This is done to protect against overflow when computing key
92    /// expiration.
93    pub fn build(self) -> Cache<K, V, RandomState> {
94        let build_hasher = RandomState::default();
95        builder_utils::ensure_expirations_or_panic(self.time_to_live, self.time_to_idle);
96        Cache::with_everything(
97            self.max_capacity,
98            self.initial_capacity,
99            build_hasher,
100            self.weigher,
101            self.time_to_live,
102            self.time_to_idle,
103        )
104    }
105
106    /// Builds a `Cache<K, V, S>`, with the given `hasher`.
107    ///
108    /// If you want to build a `SegmentedCache<K, V>`, call `segments` method  before
109    /// calling this method.
110    ///
111    /// # Panics
112    ///
113    /// Panics if configured with either `time_to_live` or `time_to_idle` higher than
114    /// 1000 years. This is done to protect against overflow when computing key
115    /// expiration.
116    pub fn build_with_hasher<S>(self, hasher: S) -> Cache<K, V, S>
117    where
118        S: BuildHasher + Clone + Send + Sync + 'static,
119    {
120        builder_utils::ensure_expirations_or_panic(self.time_to_live, self.time_to_idle);
121        Cache::with_everything(
122            self.max_capacity,
123            self.initial_capacity,
124            hasher,
125            self.weigher,
126            self.time_to_live,
127            self.time_to_idle,
128        )
129    }
130}
131
132impl<K, V, C> CacheBuilder<K, V, C> {
133    /// Sets the max capacity of the cache.
134    pub fn max_capacity(self, max_capacity: u64) -> Self {
135        Self {
136            max_capacity: Some(max_capacity),
137            ..self
138        }
139    }
140
141    /// Sets the initial capacity (number of entries) of the cache.
142    pub fn initial_capacity(self, number_of_entries: usize) -> Self {
143        Self {
144            initial_capacity: Some(number_of_entries),
145            ..self
146        }
147    }
148
149    /// Sets the weigher closure of the cache.
150    ///
151    /// The closure should take `&K` and `&V` as the arguments and returns a `u32`
152    /// representing the relative size of the entry.
153    pub fn weigher(self, weigher: impl Fn(&K, &V) -> u32 + Send + Sync + 'static) -> Self {
154        Self {
155            weigher: Some(Arc::new(weigher)),
156            ..self
157        }
158    }
159
160    /// Sets the time to live of the cache.
161    ///
162    /// A cached entry will be expired after the specified duration past from
163    /// `insert`.
164    ///
165    /// # Panics
166    ///
167    /// `CacheBuilder::build*` methods will panic if the given `duration` is longer
168    /// than 1000 years. This is done to protect against overflow when computing key
169    /// expiration.
170    pub fn time_to_live(self, duration: Duration) -> Self {
171        Self {
172            time_to_live: Some(duration),
173            ..self
174        }
175    }
176
177    /// Sets the time to idle of the cache.
178    ///
179    /// A cached entry will be expired after the specified duration past from `get`
180    /// or `insert`.
181    ///
182    /// # Panics
183    ///
184    /// `CacheBuilder::build*` methods will panic if the given `duration` is longer
185    /// than 1000 years. This is done to protect against overflow when computing key
186    /// expiration.
187    pub fn time_to_idle(self, duration: Duration) -> Self {
188        Self {
189            time_to_idle: Some(duration),
190            ..self
191        }
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::CacheBuilder;
198
199    use std::time::Duration;
200
201    #[test]
202    fn build_cache() {
203        // Cache<char, String>
204        let cache = CacheBuilder::new(100).build();
205        let policy = cache.policy();
206
207        assert_eq!(policy.max_capacity(), Some(100));
208        assert_eq!(policy.time_to_live(), None);
209        assert_eq!(policy.time_to_idle(), None);
210
211        cache.insert('a', "Alice");
212        assert_eq!(cache.get(&'a'), Some("Alice"));
213
214        let cache = CacheBuilder::new(100)
215            .time_to_live(Duration::from_secs(45 * 60))
216            .time_to_idle(Duration::from_secs(15 * 60))
217            .build();
218        let policy = cache.policy();
219
220        assert_eq!(policy.max_capacity(), Some(100));
221        assert_eq!(policy.time_to_live(), Some(Duration::from_secs(45 * 60)));
222        assert_eq!(policy.time_to_idle(), Some(Duration::from_secs(15 * 60)));
223
224        cache.insert('a', "Alice");
225        assert_eq!(cache.get(&'a'), Some("Alice"));
226    }
227
228    #[test]
229    #[should_panic(expected = "time_to_live is longer than 1000 years")]
230    fn build_cache_too_long_ttl() {
231        let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
232        let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
233        let duration = Duration::from_secs(thousand_years_secs);
234        builder
235            .time_to_live(duration + Duration::from_secs(1))
236            .build();
237    }
238
239    #[test]
240    #[should_panic(expected = "time_to_idle is longer than 1000 years")]
241    fn build_cache_too_long_tti() {
242        let thousand_years_secs: u64 = 1000 * 365 * 24 * 3600;
243        let builder: CacheBuilder<char, String, _> = CacheBuilder::new(100);
244        let duration = Duration::from_secs(thousand_years_secs);
245        builder
246            .time_to_idle(duration + Duration::from_secs(1))
247            .build();
248    }
249}