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}