cache_cache/lib.rs
1//! This caching library has been designed for specific use-cases where:
2//!
3//! * getting a "fresh" value can be time consuming and can fail (eg. IOs with hardware)
4//! * getting multiple values at once can be more efficient than getting each value independantly.
5//!
6//! Typically, its primary use was to retrieve position/speed/temperature/etc from multiple motors using serial communication. In this setup, the motors are daisy chained, and in the protocol used to communicate with them, a specific message can be used to retrieve a register value for multiple motors at once.
7//!
8
9use std::{
10 borrow::Borrow,
11 collections::HashMap,
12 error::Error,
13 hash::Hash,
14 ops::Index,
15 time::{Duration, Instant},
16};
17
18/// Cache implementation with a focus on expiry duration and reducing IO calls.
19///
20/// It is based on the [HashMap] APIs, so it can be used in almost the same way.
21///
22/// # Examples
23///
24/// ```
25/// use cache_cache::Cache;
26/// use std::{thread, time::Duration};
27///
28/// // Create a new Cache with 10ms expiry duration.
29/// let mut c = Cache::with_expiry_duration(Duration::from_millis(10));
30///
31/// // Insert a new value in the cache
32/// c.insert("present_temperature", 27.0);
33///
34/// // Retrieve it
35/// assert_eq!(c.get(&"present_temperature"), Some(&27.0));
36///
37/// // Wait for the value to get expired
38/// thread::sleep(Duration::from_millis(20));
39/// assert_eq!(c.get(&"present_temperature"), None);
40/// ```
41
42#[derive(Debug)]
43pub struct Cache<K, V> {
44 hash_map: HashMap<K, (V, Instant)>,
45 expiry_duration: Option<Duration>,
46}
47
48impl<K, V> Cache<K, V>
49where
50 K: Hash + Eq,
51 V: Clone + Copy,
52{
53 /// Creates an empty Cache where the last inserted value is kept.
54 ///
55 /// # Examples
56 ///
57 /// ```
58 /// use cache_cache::Cache;
59 /// let mut cache: Cache<&str, i32> = Cache::keep_last();
60 /// ```
61 pub fn keep_last() -> Self {
62 Cache {
63 hash_map: HashMap::new(),
64 expiry_duration: None,
65 }
66 }
67 /// Creates an empty Cache with an expiry duration.
68 ///
69 /// Each inserted value is kept until its expiration duration is reached.
70 ///
71 /// # Examples
72 ///
73 /// ```
74 /// use cache_cache::Cache;
75 /// use std::time::Duration;
76 ///
77 /// let mut cache: Cache<&str, i32> = Cache::with_expiry_duration(Duration::from_millis(10));
78 /// ```
79 pub fn with_expiry_duration(duration: Duration) -> Self {
80 Cache {
81 hash_map: HashMap::new(),
82 expiry_duration: Some(duration),
83 }
84 }
85
86 /// Gets the given key’s corresponding entry in the cache for in-place manipulation.
87 ///
88 /// Examples
89 /// ```
90 /// use cache_cache::Cache;
91 /// use std::time::Duration;
92 ///
93 /// let mut motors_temperature = Cache::with_expiry_duration(Duration::from_millis(100));
94 ///
95 /// fn get_motor_temperature(motor_id: &u8) -> f64 {
96 /// // Should actually retrieve the real value from the motor
97 /// 42.0
98 /// }
99 ///
100 /// let temp = motors_temperature.entry(11).or_insert_with(get_motor_temperature);
101 /// assert_eq!(motors_temperature.get(&11), Some(&42.0));
102 pub fn entry(&mut self, key: K) -> Entry<'_, K, V> {
103 match self.get(&key) {
104 Some(&v) => Entry::Occupied(OccupiedEntry { k: key, v }),
105 None => Entry::Vacant(VacantEntry {
106 k: key,
107 cache: self,
108 }),
109 }
110 }
111 /// Gets the given keys' corresponding entries in the cache for in-place manipulation.
112 ///
113 /// This is mostly useful if you want to modify multiple entries at once. For instance, because you can use a single IO call to update all those entries instead of having a call for each entry.
114 ///
115 /// Examples
116 /// ```
117 /// use cache_cache::Cache;
118 /// use std::{error::Error, time::Duration};
119 ///
120 /// fn get_position(ids: &[u8]) -> Result<Vec<f64>, Box<dyn Error>> {
121 /// // For simplicity, this function always work.
122 /// // But it's a mockup for a real world scenario where hardware IO can fail.
123 /// Ok(ids.iter().map(|&id| id as f64 * 10.0).collect())
124 /// }
125 ///
126 /// let mut present_position = Cache::with_expiry_duration(Duration::from_millis(10));
127 ///
128 /// present_position.insert(10, 0.0);
129 ///
130 /// let pos = present_position
131 /// .entries(&[10, 11, 12])
132 /// .or_try_insert_with(get_position);
133 ///
134 /// assert!(pos.is_ok());
135 /// assert_eq!(pos.unwrap(), vec![0.0, 110.0, 120.0]);
136 /// ```
137 pub fn entries<'a>(&'a mut self, keys: &'a [K]) -> Entries<'_, K, V> {
138 Entries { keys, cache: self }
139 }
140
141 fn has_expired(expiry_duration: Option<Duration>, t: &Instant) -> bool {
142 match expiry_duration {
143 Some(expiry) => t.elapsed() > expiry,
144 None => false,
145 }
146 }
147 /// Returns a reference to the value corresponding to the key if it has not expired.
148 ///
149 /// # Examples
150 ///
151 /// ```
152 /// use cache_cache::Cache;
153 ///
154 /// let mut cache: Cache<&str, f64> = Cache::keep_last();
155 /// cache.insert("position", 0.23);
156 /// assert_eq!(cache.get(&"position"), Some(&0.23));
157 /// ```
158 pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
159 where
160 K: Borrow<Q>,
161 Q: Hash + Eq,
162 {
163 match self.hash_map.get(k) {
164 Some((v, t)) => match Self::has_expired(self.expiry_duration, t) {
165 true => None,
166 false => Some(v),
167 },
168 None => None,
169 }
170 }
171 /// Inserts a key-value pair into the cache.
172 ///
173 /// If the cache did not have this key present, None is returned.
174 /// If the cache did have this key present, the value is updated, and the old value (expired or not) is returned.
175 ///
176 /// Examples
177 /// ```
178 /// use cache_cache::Cache;
179 ///
180 /// let mut cache = Cache::keep_last();
181 /// assert_eq!(cache.insert(10, "a"), None);
182 /// assert_eq!(cache.insert(10, "b"), Some("a"));
183 /// assert_eq!(cache[&10], "b");
184 /// ```
185 pub fn insert(&mut self, k: K, v: V) -> Option<V> {
186 self.hash_map.insert(k, (v, Instant::now())).map(|v| v.0)
187 }
188}
189
190impl<K, Q: ?Sized, V> Index<&Q> for Cache<K, V>
191where
192 K: Eq + Hash + Borrow<Q>,
193 Q: Eq + Hash,
194 V: Clone + Copy,
195{
196 type Output = V;
197
198 /// Returns a reference to the value corresponding to the supplied key.
199 ///
200 /// # Panics
201 ///
202 /// Panics if the key is not present in the `Cache`.
203 fn index(&self, index: &Q) -> &Self::Output {
204 self.get(index).expect("no entry found for key")
205 }
206}
207
208#[derive(Debug)]
209/// A view into a single entry in a cache, which may either be vacant or occupied.
210///
211/// This enum is constructed from the entry method on [Cache].
212pub enum Entry<'a, K: 'a, V: 'a> {
213 /// An occupied entry.
214 Occupied(OccupiedEntry<K, V>),
215 /// A vacant entry.
216 Vacant(VacantEntry<'a, K, V>),
217}
218
219impl<'a, K, V> Entry<'a, K, V>
220where
221 K: Hash + Eq + Copy,
222 V: Clone + Copy,
223{
224 /// Ensures a value is in the entry by inserting the default if empty, and returns a copy of the value in the entry.
225 ///
226 /// Examples
227 /// ```
228 /// use cache_cache::Cache;
229 ///
230 /// let mut target_positions = Cache::keep_last();
231 ///
232 /// target_positions.entry(10).or_insert(0);
233 /// assert_eq!(target_positions[&10], 0);
234 pub fn or_insert(self, default: V) -> V {
235 match self {
236 Entry::Occupied(entry) => entry.v,
237 Entry::Vacant(entry) => entry.insert(default),
238 }
239 }
240 /// Ensures a value is in the entry by inserting the result of the default function if empty, and returns a copy of the value in the entry.
241 ///
242 /// In contrary to [HashMap] API, the default function takes the key as argument. As shown in the example, it makes the IO call simpler. It does require the key to implement the Copy trait though.
243 ///
244 /// Examples
245 /// ```
246 /// use cache_cache::Cache;
247 ///
248 /// let mut torque_enable: Cache<u8, bool> = Cache::keep_last();
249 ///
250 /// torque_enable.entry(20).or_insert_with(|id| false);
251 /// assert_eq!(torque_enable[&20], false);
252 /// ```
253 pub fn or_insert_with<F: FnOnce(&K) -> V>(self, default: F) -> V {
254 let k = *self.key();
255
256 match self {
257 Entry::Occupied(entry) => entry.v,
258 Entry::Vacant(entry) => entry.insert(default(&k)),
259 }
260 }
261 /// Tries inserting a value in the entry (if empty) with the default function and returns a [Result] of the value in the entry or the error encounter by the default function.
262 ///
263 /// Examples
264 /// ```
265 /// use cache_cache::Cache;
266 /// use std::{error::Error, fmt};
267 ///
268 /// #[derive(Debug)]
269 /// struct MyDummyIOError;
270 /// impl fmt::Display for MyDummyIOError {
271 /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
272 /// write!(f, "my io error")
273 /// }
274 /// }
275 /// impl Error for MyDummyIOError {}
276 ///
277 /// fn enable_torque(id: &u8) -> Result<bool, Box<dyn Error>> {
278 /// // Send hardware command that could fail, something like
279 /// // serial_send_torque_on_command(...)?;
280 ///
281 /// // For example purposes, we suppose here that our method:
282 /// // * will work for id within 0...10
283 /// // * fail for other
284 /// if *id > 10 {
285 /// Err(Box::new(MyDummyIOError))
286 /// }
287 /// else {
288 /// Ok(true)
289 /// }
290 /// }
291 ///
292 /// let mut torque_enable = Cache::keep_last();
293 ///
294 /// let res = torque_enable.entry(5).or_try_insert_with(enable_torque);
295 /// assert!(res.is_ok());
296 /// assert_eq!(res.unwrap(), true);
297 ///
298 /// let res = torque_enable.entry(20).or_try_insert_with(enable_torque);
299 /// assert!(res.is_err());
300
301 /// ```
302 pub fn or_try_insert_with<F: FnOnce(&K) -> Result<V, Box<dyn Error>>>(
303 self,
304 default: F,
305 ) -> Result<V, Box<dyn Error>> {
306 let k = *self.key();
307
308 match self {
309 Entry::Occupied(entry) => Ok(entry.v),
310 Entry::Vacant(entry) => match default(&k) {
311 Ok(v) => Ok(entry.insert(v)),
312 Err(e) => Err(e),
313 },
314 }
315 }
316
317 /// Returns a reference to this entry’s key.
318 ///
319 /// Examples
320 /// ```
321 /// use cache_cache::Cache;
322 ///
323 /// let mut cache: Cache<&str, u32> = Cache::keep_last();
324 /// assert_eq!(cache.entry("speed").key(), &"speed");
325 /// ```
326 pub fn key(&self) -> &K {
327 match self {
328 Entry::Occupied(entry) => &entry.k,
329 Entry::Vacant(entry) => &entry.k,
330 }
331 }
332}
333
334#[derive(Debug)]
335/// A view into an occupied entry in a [Cache]. It is part of the [Entry] enum.
336pub struct OccupiedEntry<K, V> {
337 k: K,
338 v: V,
339}
340
341#[derive(Debug)]
342/// A view into a vacant entry in a [Cache]. It is part of the [Entry] enum.
343pub struct VacantEntry<'a, K, V> {
344 k: K,
345 cache: &'a mut Cache<K, V>,
346}
347
348impl<'a, K, V> VacantEntry<'a, K, V>
349where
350 K: Hash + Eq + Clone,
351 V: Clone + Copy,
352{
353 fn insert(self, v: V) -> V {
354 self.cache.insert(self.k.clone(), v);
355 *self.cache.get(&self.k).unwrap()
356 }
357}
358
359#[derive(Debug)]
360/// A view into multiple [Entry] in a cache.
361pub struct Entries<'a, K: 'a, V: 'a> {
362 keys: &'a [K],
363 cache: &'a mut Cache<K, V>,
364}
365impl<'a, K, V> Entries<'a, K, V>
366where
367 K: Hash + Eq + Copy,
368 V: Clone + Copy,
369{
370 /// Ensures a value is in the entries by inserting the default if empty, and returns the value in the entries.
371 ///
372 /// Examples
373 /// ```
374 /// use cache_cache::Cache;
375 ///
376 /// let mut target_positions = Cache::keep_last();
377 ///
378 /// target_positions.insert(11, 90);
379 ///
380 /// target_positions.entries(&[10, 11, 12]).or_insert(0);
381 /// assert_eq!(target_positions[&10], 0);
382 /// assert_eq!(target_positions[&11], 90);
383 /// assert_eq!(target_positions[&12], 0);
384 pub fn or_insert(self, default: V) -> Vec<V> {
385 let mut values = Vec::new();
386
387 for &k in self.keys {
388 match self.cache.get(&k) {
389 Some(v) => {
390 values.push(*v);
391 }
392 None => {
393 self.cache.insert(k, default);
394 values.push(default);
395 }
396 }
397 }
398
399 values
400
401 // for &k in self.keys {
402 // self.cache.entry(k).or_insert(default.clone());
403 // }
404
405 // self.keys
406 // .iter()
407 // .map(|k| self.cache.get(k).unwrap())
408 // .collect()
409 }
410 /// Ensures a value is in the entries by inserting the result of the default function if empty, and returns the value in the entries.
411 ///
412 /// In contrary to [HashMap] API, the default function takes the missing keys as argument. As shown in the example, it makes the IO call simpler. It does require the key to implement the Copy trait though.
413 ///
414 /// Examples
415 /// ```
416 /// use cache_cache::Cache;
417 /// use std::{error::Error, time::Duration};
418 ///
419 /// let mut present_position = Cache::with_expiry_duration(Duration::from_millis(10));
420 ///
421 /// present_position.insert(10, 0.0);
422 ///
423 /// let pos = present_position
424 /// .entries(&[10, 11, 12])
425 /// .or_insert_with(|ids| ids.iter().map(|&id| id as f64 * 10.0).collect());
426 ///
427 /// assert_eq!(pos, vec![0.0, 110.0, 120.0]);
428 /// ```
429 pub fn or_insert_with<F: FnOnce(&[K]) -> Vec<V>>(self, default: F) -> Vec<V> {
430 self.or_try_insert_with(|missing| Ok(default(missing)))
431 .unwrap()
432 }
433 /// Tries inserting a value in the entries (if empty) with the default function and returns a [Result] of the value in the entries or the error encounter by the default function.
434 ///
435 /// Examples
436 /// ```
437 /// use cache_cache::Cache;
438 /// use std::{error::Error, time::Duration};
439 ///
440 /// fn get_position(ids: &[u8]) -> Result<Vec<f64>, Box<dyn Error>> {
441 /// // For simplicity, this function always work.
442 /// // But it's a mockup for a real world scenario where hardware IO can fail.
443 /// Ok(ids.iter().map(|&id| id as f64 * 10.0).collect())
444 /// }
445 ///
446 /// let mut present_position = Cache::with_expiry_duration(Duration::from_millis(10));
447 ///
448 /// present_position.insert(10, 0.0);
449 ///
450 /// let pos = present_position
451 /// .entries(&[10, 11, 12])
452 /// .or_try_insert_with(get_position);
453 ///
454 /// assert!(pos.is_ok());
455 /// assert_eq!(pos.unwrap(), vec![0.0, 110.0, 120.0]);
456 /// ```
457 pub fn or_try_insert_with<F: FnOnce(&[K]) -> Result<Vec<V>, Box<dyn Error>>>(
458 self,
459 default: F,
460 ) -> Result<Vec<V>, Box<dyn Error>> {
461 let mut values = HashMap::new();
462 let mut missing = Vec::new();
463
464 for k in self.keys {
465 match self.cache.get(k) {
466 Some(&v) => {
467 values.insert(k, v);
468 }
469 None => {
470 missing.push(*k);
471 }
472 }
473 }
474
475 if !missing.is_empty() {
476 let missing_values = default(&missing)?;
477
478 assert_eq!(missing.len(), missing_values.len());
479
480 for (k, v) in missing.iter().zip(missing_values) {
481 self.cache.insert(*k, v);
482 values.insert(k, v);
483 }
484 }
485
486 Ok(self.keys.iter().map(|k| values[k]).collect())
487 }
488}