Skip to main content

tiny_cache/
fifo_cache.rs

1use std::collections::VecDeque;
2
3use rustc_hash::FxHashMap;
4
5/// A [First In, First Out (FIFO)](https://en.wikipedia.org/wiki/Cache_replacement_policies#First_in_first_out_(FIFO)) cache for reusing expensive results.
6/// Keeps up to your chosen limit and removes the oldest added result when full.
7///
8/// # Example
9///
10/// ```
11/// use tiny_cache::FifoCache;
12///
13/// let mut cache = FifoCache::new(2);
14/// cache.insert("first", 10);
15/// cache.insert("second", 20);
16/// assert_eq!(cache.get(&"first"), Some(&10));
17///
18/// cache.insert("third", 30);
19/// assert!(cache.get(&"first").is_none());
20/// assert_eq!(cache.get(&"third"), Some(&30));
21/// assert_eq!(cache.len(), 2);
22/// ```
23pub struct FifoCache<K, V> {
24    map: FxHashMap<K, V>,
25    order: VecDeque<K>,
26    capacity: usize,
27}
28
29impl<K, V> FifoCache<K, V> {
30    pub fn new(capacity: usize) -> Self {
31        Self {
32            map: FxHashMap::default(),
33            order: VecDeque::new(),
34            capacity,
35        }
36    }
37}
38
39impl<K, V> FifoCache<K, V>
40where
41    K: Clone + Eq + std::hash::Hash,
42{
43    /// Changes how many results to keep, removing the oldest added results if the limit shrinks.
44    pub fn set_capacity(&mut self, capacity: usize) {
45        self.capacity = capacity;
46        while self.order.len() > capacity {
47            if let Some(old_key) = self.order.pop_front() {
48                self.map.remove(&old_key);
49            }
50        }
51    }
52
53    /// Saves a result to reuse later, removing the oldest added result if there is no room.
54    /// Replacing an existing result does not move it to the back of the line.
55    pub fn insert(&mut self, key: K, value: V) {
56        if self.capacity == 0 {
57            return;
58        }
59
60        if self.map.contains_key(&key) {
61            self.map.insert(key, value);
62        } else {
63            if self.order.len() >= self.capacity
64                && let Some(old_key) = self.order.pop_front()
65            {
66                self.map.remove(&old_key);
67            }
68            self.order.push_back(key.clone());
69            self.map.insert(key, value);
70        }
71    }
72
73    pub fn get(&self, key: &K) -> Option<&V> {
74        self.map.get(key)
75    }
76
77    /// Lets you update a saved result without creating a replacement or changing its place in line.
78    pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
79        self.map.get_mut(key)
80    }
81
82    pub fn capacity(&self) -> usize {
83        self.capacity
84    }
85
86    pub fn len(&self) -> usize {
87        self.map.len()
88    }
89
90    pub fn is_empty(&self) -> bool {
91        self.map.is_empty()
92    }
93
94    pub fn clear(&mut self) {
95        self.map.clear();
96        self.order.clear();
97    }
98}
99
100impl<K, V> std::fmt::Debug for FifoCache<K, V>
101where
102    K: std::fmt::Debug,
103    V: std::fmt::Debug,
104{
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        f.debug_struct("FifoCache")
107            .field("map", &self.map)
108            .field("order", &self.order)
109            .field("capacity", &self.capacity)
110            .finish()
111    }
112}
113
114#[cfg(test)]
115mod test {
116    use crate::FifoCache;
117
118    #[test]
119    fn custom_capacity() {
120        let mut cache = FifoCache::new(2);
121        cache.insert(1, "first");
122        cache.insert(2, "second");
123        cache.insert(1, "updated");
124        assert_eq!(cache.get(&1), Some(&"updated"));
125        assert_eq!(cache.len(), 2);
126
127        cache.insert(3, "third");
128        assert_eq!(cache.get(&1), None);
129        assert_eq!(cache.get(&2), Some(&"second"));
130        assert_eq!(cache.get(&3), Some(&"third"));
131        assert_eq!(cache.len(), 2);
132    }
133
134    #[test]
135    fn zero_capacity_keeps_nothing() {
136        let mut cache = FifoCache::new(0);
137        for key in 0..3 {
138            cache.insert(key, key);
139        }
140        assert!(cache.is_empty());
141        assert!(cache.order.is_empty());
142        assert_eq!(cache.get(&0), None);
143    }
144
145    #[test]
146    fn clear_preserves_custom_capacity() {
147        let mut cache = FifoCache::new(1);
148        cache.insert(1, "first");
149        cache.clear();
150        assert!(cache.is_empty());
151
152        cache.insert(2, "second");
153        cache.insert(3, "third");
154        assert_eq!(cache.get(&1), None);
155        assert_eq!(cache.get(&2), None);
156        assert_eq!(cache.get(&3), Some(&"third"));
157        assert_eq!(cache.len(), 1);
158    }
159
160    #[test]
161    fn shrinking_removes_oldest_results() {
162        let mut cache = FifoCache::new(4);
163        for key in 0..4 {
164            cache.insert(key, key * 10);
165        }
166        cache.insert(0, 100);
167
168        cache.set_capacity(2);
169        assert_eq!(cache.capacity(), 2);
170        assert_eq!(cache.len(), 2);
171        assert_eq!(cache.get(&0), None);
172        assert_eq!(cache.get(&1), None);
173        assert_eq!(cache.get(&2), Some(&20));
174        assert_eq!(cache.get(&3), Some(&30));
175
176        cache.insert(4, 40);
177        assert_eq!(cache.get(&2), None);
178        assert_eq!(cache.get(&3), Some(&30));
179        assert_eq!(cache.get(&4), Some(&40));
180        assert_eq!(cache.len(), 2);
181    }
182
183    #[test]
184    fn growing_preserves_saved_results() {
185        let mut cache = FifoCache::new(1);
186        cache.insert(1, "first");
187        cache.set_capacity(3);
188        cache.insert(2, "second");
189        cache.insert(3, "third");
190
191        assert_eq!(cache.capacity(), 3);
192        assert_eq!(cache.len(), 3);
193        assert_eq!(cache.get(&1), Some(&"first"));
194        assert_eq!(cache.get(&2), Some(&"second"));
195        assert_eq!(cache.get(&3), Some(&"third"));
196    }
197
198    #[test]
199    fn shrinking_to_zero_then_growing() {
200        let mut cache = FifoCache::new(2);
201        cache.insert(1, "first");
202        cache.insert(2, "second");
203        cache.set_capacity(0);
204        cache.insert(3, "third");
205
206        assert_eq!(cache.capacity(), 0);
207        assert!(cache.is_empty());
208        assert!(cache.order.is_empty());
209
210        cache.set_capacity(1);
211        cache.insert(4, "fourth");
212        assert_eq!(cache.get(&1), None);
213        assert_eq!(cache.get(&4), Some(&"fourth"));
214        assert_eq!(cache.len(), 1);
215    }
216
217    #[test]
218    fn unchanged_capacity_preserves_saved_results() {
219        let mut cache = FifoCache::new(2);
220        cache.insert(1, "first");
221        cache.insert(2, "second");
222        cache.set_capacity(2);
223
224        assert_eq!(cache.len(), 2);
225        assert_eq!(cache.get(&1), Some(&"first"));
226        assert_eq!(cache.get(&2), Some(&"second"));
227    }
228}