Skip to main content

cache_mod/
lru.rs

1//! Least-Recently-Used (LRU) cache — the reference implementation of
2//! [`Cache`](crate::Cache).
3
4use core::hash::Hash;
5use std::collections::{HashMap, VecDeque};
6use std::num::NonZeroUsize;
7use std::sync::{Mutex, MutexGuard};
8
9use crate::cache::Cache;
10use crate::error::CacheError;
11
12/// A bounded, thread-safe LRU cache.
13///
14/// On insert overflow the least-recently-accessed entry is evicted. Both
15/// [`get`](Cache::get) and [`insert`](Cache::insert) count as accesses and
16/// promote the affected entry to most-recently-used.
17///
18/// This is the 0.2.0 reference implementation: correct, `&self`-everywhere,
19/// `Mutex`-guarded. A lock-free, arena-backed implementation lands in 0.5.0
20/// without changing this public surface.
21///
22/// # Example
23///
24/// ```
25/// use cache_mod::{Cache, LruCache};
26///
27/// let cache: LruCache<u32, &'static str> = LruCache::new(2).expect("capacity > 0");
28///
29/// cache.insert(1, "one");
30/// cache.insert(2, "two");
31/// assert_eq!(cache.get(&1), Some("one")); // 1 is now MRU, 2 is LRU
32///
33/// cache.insert(3, "three"); // evicts 2
34/// assert_eq!(cache.get(&2), None);
35/// assert_eq!(cache.get(&1), Some("one"));
36/// assert_eq!(cache.get(&3), Some("three"));
37/// ```
38pub struct LruCache<K, V> {
39    capacity: NonZeroUsize,
40    inner: Mutex<Inner<K, V>>,
41}
42
43struct Inner<K, V> {
44    /// Storage of live entries.
45    map: HashMap<K, V>,
46    /// Access order. Front = most-recently-used, back = least-recently-used.
47    order: VecDeque<K>,
48}
49
50impl<K, V> LruCache<K, V>
51where
52    K: Eq + Hash + Clone,
53    V: Clone,
54{
55    /// Creates a cache with the given capacity.
56    ///
57    /// Returns [`CacheError::InvalidCapacity`] if `capacity == 0`.
58    ///
59    /// # Example
60    ///
61    /// ```
62    /// use cache_mod::LruCache;
63    ///
64    /// let cache: LruCache<String, u32> = LruCache::new(128).expect("capacity > 0");
65    /// ```
66    pub fn new(capacity: usize) -> Result<Self, CacheError> {
67        let cap = NonZeroUsize::new(capacity).ok_or(CacheError::InvalidCapacity)?;
68        Ok(Self::with_capacity(cap))
69    }
70
71    /// Creates a cache with the given non-zero capacity. Infallible.
72    ///
73    /// # Example
74    ///
75    /// ```
76    /// use std::num::NonZeroUsize;
77    /// use cache_mod::LruCache;
78    ///
79    /// let cap = NonZeroUsize::new(64).expect("64 != 0");
80    /// let cache: LruCache<String, u32> = LruCache::with_capacity(cap);
81    /// ```
82    pub fn with_capacity(capacity: NonZeroUsize) -> Self {
83        let cap = capacity.get();
84        Self {
85            capacity,
86            inner: Mutex::new(Inner {
87                map: HashMap::with_capacity(cap),
88                order: VecDeque::with_capacity(cap),
89            }),
90        }
91    }
92
93    /// Recovers a [`MutexGuard`] even if the lock has been poisoned by a
94    /// panic in another thread. The cache's invariants are not weakened by
95    /// a poisoned lock: every operation re-establishes consistency between
96    /// `map` and `order` before returning.
97    fn lock_inner(&self) -> MutexGuard<'_, Inner<K, V>> {
98        match self.inner.lock() {
99            Ok(guard) => guard,
100            Err(poisoned) => poisoned.into_inner(),
101        }
102    }
103}
104
105impl<K, V> Cache<K, V> for LruCache<K, V>
106where
107    K: Eq + Hash + Clone,
108    V: Clone,
109{
110    fn get(&self, key: &K) -> Option<V> {
111        let mut inner = self.lock_inner();
112        let value = inner.map.get(key)?.clone();
113        promote(&mut inner.order, key);
114        Some(value)
115    }
116
117    fn insert(&self, key: K, value: V) -> Option<V> {
118        let mut inner = self.lock_inner();
119        let old = inner.map.insert(key.clone(), value);
120        if old.is_some() {
121            promote(&mut inner.order, &key);
122        } else {
123            inner.order.push_front(key);
124            while inner.order.len() > self.capacity.get() {
125                if let Some(evicted) = inner.order.pop_back() {
126                    let _ = inner.map.remove(&evicted);
127                }
128            }
129        }
130        old
131    }
132
133    fn remove(&self, key: &K) -> Option<V> {
134        let mut inner = self.lock_inner();
135        let value = inner.map.remove(key)?;
136        if let Some(pos) = inner.order.iter().position(|k| k == key) {
137            let _ = inner.order.remove(pos);
138        }
139        Some(value)
140    }
141
142    fn contains_key(&self, key: &K) -> bool {
143        self.lock_inner().map.contains_key(key)
144    }
145
146    fn len(&self) -> usize {
147        self.lock_inner().map.len()
148    }
149
150    fn clear(&self) {
151        let mut inner = self.lock_inner();
152        inner.map.clear();
153        inner.order.clear();
154    }
155
156    fn capacity(&self) -> usize {
157        self.capacity.get()
158    }
159}
160
161/// Moves `key` to the front of `order` if it is present. No-op otherwise.
162fn promote<K: Eq>(order: &mut VecDeque<K>, key: &K) {
163    if let Some(pos) = order.iter().position(|k| k == key) {
164        if let Some(k) = order.remove(pos) {
165            order.push_front(k);
166        }
167    }
168}