uluru/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#![no_std]
6#![deny(unsafe_code)]
7
8//! A simple, fast, least-recently-used (LRU) cache.
9//!
10//! [`LRUCache`] uses a fixed-capacity array for storage. It provides `O(1)` insertion, and `O(n)`
11//! lookup.  It does not require an allocator and can be used in `no_std` crates.
12//!
13//! See the [`LRUCache`] docs for details.
14
15use arrayvec::ArrayVec;
16use core::mem::replace;
17
18#[cfg(test)]
19mod tests;
20
21/// A LRU cache using a statically-sized array for storage.
22///
23/// `LRUCache` uses a fixed-capacity array for storage. It provides `O(1)` insertion, and `O(n)`
24/// lookup.
25///
26/// All items are stored inline within the `LRUCache`, so it does not impose any heap allocation or
27/// indirection.  A linked list is used to record the cache order, so the items themselves do not
28/// need to be moved when the order changes.  (This is important for speed if the items are large.)
29///
30/// # Example
31///
32/// ```
33/// use uluru::LRUCache;
34///
35/// struct MyValue {
36///     id: u32,
37///     name: &'static str,
38/// }
39///
40/// // A cache with a capacity of three.
41/// type MyCache = LRUCache<MyValue, 3>;
42///
43/// // Create an empty cache, then insert some items.
44/// let mut cache = MyCache::default();
45/// cache.insert(MyValue { id: 1, name: "Mercury" });
46/// cache.insert(MyValue { id: 2, name: "Venus" });
47/// cache.insert(MyValue { id: 3, name: "Earth" });
48///
49/// // Use the `find` method to retrieve an item from the cache.
50/// // This also "touches" the item, marking it most-recently-used.
51/// let item = cache.find(|x| x.id == 1);
52/// assert_eq!(item.unwrap().name, "Mercury");
53///
54/// // If the cache is full, inserting a new item evicts the least-recently-used item:
55/// cache.insert(MyValue { id: 4, name: "Mars" });
56/// assert!(cache.find(|x| x.id == 2).is_none());
57/// ```
58#[derive(Debug, Clone)]
59pub struct LRUCache<T, const N: usize> {
60    /// The most-recently-used entry is at index `head`. The entries form a linked list, linked to
61    /// each other by indices within the `entries` array.  After an entry is added to the array,
62    /// its index never changes, so these links are never invalidated.
63    entries: ArrayVec<Entry<T>, N>,
64    /// Index of the first entry. If the cache is empty, ignore this field.
65    head: u16,
66    /// Index of the last entry. If the cache is empty, ignore this field.
67    tail: u16,
68}
69
70/// An entry in an `LRUCache`.
71#[derive(Debug, Clone)]
72struct Entry<T> {
73    val: T,
74    /// Index of the previous entry. If this entry is the head, ignore this field.
75    prev: u16,
76    /// Index of the next entry. If this entry is the tail, ignore this field.
77    next: u16,
78}
79
80impl<T, const N: usize> Default for LRUCache<T, N> {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86impl<T, const N: usize> LRUCache<T, N> {
87    /// Create an empty cache.
88    pub const fn new() -> Self {
89        assert!(N < u16::MAX as usize, "Capacity overflow");
90        LRUCache {
91            entries: ArrayVec::new_const(),
92            head: 0,
93            tail: 0,
94        }
95    }
96
97    /// Insert a given key in the cache.
98    ///
99    /// This item becomes the front (most-recently-used) item in the cache.  If the cache is full,
100    /// the back (least-recently-used) item will be removed and returned.
101    pub fn insert(&mut self, val: T) -> Option<T> {
102        let new_entry = Entry {
103            val,
104            prev: 0,
105            next: 0,
106        };
107
108        // If the cache is full, replace the oldest entry. Otherwise, add an entry.
109        if self.entries.is_full() {
110            let i = self.pop_back();
111            let old_entry = replace(self.entry(i), new_entry);
112            self.push_front(i);
113            Some(old_entry.val)
114        } else {
115            let i = self.entries.len() as u16;
116            self.entries.push(new_entry);
117            self.push_front(i);
118            None
119        }
120    }
121
122    /// Returns the first item in the cache that matches the given predicate.
123    /// Touches the result (makes it most-recently-used) on a hit.
124    pub fn find<F>(&mut self, pred: F) -> Option<&mut T>
125    where
126        F: FnMut(&T) -> bool,
127    {
128        if self.touch(pred) {
129            self.front_mut()
130        } else {
131            None
132        }
133    }
134
135    /// Performs a lookup on the cache with the given test routine. Touches
136    /// the result on a hit.
137    pub fn lookup<F, R>(&mut self, mut pred: F) -> Option<R>
138    where
139        F: FnMut(&mut T) -> Option<R>,
140    {
141        let mut iter = self.iter_mut();
142        while let Some((i, val)) = iter.next() {
143            if let Some(r) = pred(val) {
144                self.touch_index(i);
145                return Some(r);
146            }
147        }
148        None
149    }
150
151    /// Returns the number of elements in the cache.
152    #[inline]
153    pub fn len(&self) -> usize {
154        self.entries.len()
155    }
156
157    /// Returns true if the cache is empty.
158    #[inline]
159    pub fn is_empty(&self) -> bool {
160        self.entries.is_empty()
161    }
162
163    /// Evict all elements from the cache.
164    #[inline]
165    pub fn clear(&mut self) {
166        self.entries.clear();
167    }
168
169    /// Returns the front entry in the list (most recently used).
170    pub fn front(&self) -> Option<&T> {
171        self.entries.get(self.head as usize).map(|e| &e.val)
172    }
173
174    /// Returns a mutable reference to the front entry in the list (most recently used).
175    pub fn front_mut(&mut self) -> Option<&mut T> {
176        self.entries.get_mut(self.head as usize).map(|e| &mut e.val)
177    }
178
179    /// Returns the n-th entry in the list (most recently used).
180    pub fn get(&self, index: usize) -> Option<&T> {
181        self.iter().nth(index)
182    }
183
184    /// Touches the first item in the cache that matches the given predicate (marks it as
185    /// most-recently-used).
186    /// Returns `true` on a hit, `false` if no matches.
187    pub fn touch<F>(&mut self, mut pred: F) -> bool
188    where
189        F: FnMut(&T) -> bool,
190    {
191        let mut iter = self.iter_mut();
192        while let Some((i, val)) = iter.next() {
193            if pred(val) {
194                self.touch_index(i);
195                return true;
196            }
197        }
198        false
199    }
200
201    /// Iterate over the contents of this cache in order from most-recently-used to
202    /// least-recently-used.
203    pub fn iter(&self) -> Iter<'_, T, N> {
204        Iter {
205            pos: self.head,
206            cache: self,
207        }
208    }
209
210    /// Iterate mutably over the contents of this cache in order from most-recently-used to
211    /// least-recently-used.
212    fn iter_mut(&mut self) -> IterMut<'_, T, N> {
213        IterMut {
214            pos: self.head,
215            cache: self,
216        }
217    }
218
219    /// Touch a given entry, putting it first in the list.
220    #[inline]
221    fn touch_index(&mut self, i: u16) {
222        if i != self.head {
223            self.remove(i);
224            self.push_front(i);
225        }
226    }
227
228    #[inline(always)]
229    fn entry(&mut self, i: u16) -> &mut Entry<T> {
230        &mut self.entries[i as usize]
231    }
232
233    /// Remove an entry from the linked list.
234    ///
235    /// Note: This only unlinks the entry from the list; it does not remove it from the array.
236    fn remove(&mut self, i: u16) {
237        let prev = self.entry(i).prev;
238        let next = self.entry(i).next;
239
240        if i == self.head {
241            self.head = next;
242        } else {
243            self.entry(prev).next = next;
244        }
245
246        if i == self.tail {
247            self.tail = prev;
248        } else {
249            self.entry(next).prev = prev;
250        }
251    }
252
253    /// Insert a new entry at the head of the list.
254    fn push_front(&mut self, i: u16) {
255        if self.entries.len() == 1 {
256            self.tail = i;
257        } else {
258            self.entry(i).next = self.head;
259            self.entry(self.head).prev = i;
260        }
261        self.head = i;
262    }
263
264    /// Remove the last entry from the linked list. Returns the index of the removed entry.
265    ///
266    /// Note: This only unlinks the entry from the list; it does not remove it from the array.
267    fn pop_back(&mut self) -> u16 {
268        let new_tail = self.entry(self.tail).prev;
269        replace(&mut self.tail, new_tail)
270    }
271}
272
273/// Mutable iterator over values in an `LRUCache`, from most-recently-used to least-recently-used.
274struct IterMut<'a, T, const N: usize> {
275    cache: &'a mut LRUCache<T, N>,
276    pos: u16,
277}
278
279impl<'a, T, const N: usize> IterMut<'a, T, N> {
280    fn next(&mut self) -> Option<(u16, &mut T)> {
281        let index = self.pos;
282        let entry = self.cache.entries.get_mut(index as usize)?;
283
284        self.pos = if index == self.cache.tail {
285            N as u16 // Point past the end of the array to signal we are done.
286        } else {
287            entry.next
288        };
289        Some((index, &mut entry.val))
290    }
291}
292
293/// Iterator over values in an [`LRUCache`], from most-recently-used to least-recently-used.
294pub struct Iter<'a, T, const N: usize> {
295    cache: &'a LRUCache<T, N>,
296    pos: u16,
297}
298
299impl<'a, T, const N: usize> Iterator for Iter<'a, T, N> {
300    type Item = &'a T;
301
302    fn next(&mut self) -> Option<&'a T> {
303        let entry = self.cache.entries.get(self.pos as usize)?;
304
305        self.pos = if self.pos == self.cache.tail {
306            N as u16 // Point past the end of the array to signal we are done.
307        } else {
308            entry.next
309        };
310        Some(&entry.val)
311    }
312}