Skip to main content

cache_mod/
lru.rs

1//! Least-Recently-Used (LRU) cache — arena-backed reference implementation.
2
3use core::hash::Hash;
4use std::collections::HashMap;
5use std::num::NonZeroUsize;
6use std::sync::Mutex;
7
8use crate::cache::Cache;
9use crate::error::CacheError;
10use crate::util::MutexExt;
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/// 0.6.0 implementation: an index-stable arena of nodes plus a `HashMap<K, usize>`
19/// keyed lookup. Promote and evict are O(1); the 0.5.x `VecDeque` scan is gone.
20/// Lock strategy is still a single `Mutex<Inner>`; the sharded / lock-free
21/// upgrade lands in 0.7.0 without changing this public surface.
22///
23/// # Example
24///
25/// ```
26/// use cache_mod::{Cache, LruCache};
27///
28/// let cache: LruCache<u32, &'static str> = LruCache::new(2).expect("capacity > 0");
29///
30/// cache.insert(1, "one");
31/// cache.insert(2, "two");
32/// assert_eq!(cache.get(&1), Some("one")); // 1 is now MRU, 2 is LRU
33///
34/// cache.insert(3, "three"); // evicts 2
35/// assert_eq!(cache.get(&2), None);
36/// assert_eq!(cache.get(&1), Some("one"));
37/// assert_eq!(cache.get(&3), Some("three"));
38/// ```
39pub struct LruCache<K, V> {
40    capacity: NonZeroUsize,
41    inner: Mutex<Inner<K, V>>,
42}
43
44struct Node<K, V> {
45    key: K,
46    value: V,
47    prev: Option<usize>,
48    next: Option<usize>,
49}
50
51struct Inner<K, V> {
52    /// Slab of nodes. `None` slots are on the free list.
53    nodes: Vec<Option<Node<K, V>>>,
54    /// Free indices in `nodes` available for reuse.
55    free: Vec<usize>,
56    /// Most-recently-used node index, if any.
57    head: Option<usize>,
58    /// Least-recently-used node index, if any.
59    tail: Option<usize>,
60    /// Key → arena index.
61    map: HashMap<K, usize>,
62}
63
64impl<K, V> Inner<K, V>
65where
66    K: Eq + Hash + Clone,
67{
68    fn with_capacity(cap: usize) -> Self {
69        Self {
70            nodes: Vec::with_capacity(cap),
71            free: Vec::new(),
72            head: None,
73            tail: None,
74            map: HashMap::with_capacity(cap),
75        }
76    }
77
78    /// Allocate an arena slot for `node`, returning its stable index.
79    fn alloc(&mut self, node: Node<K, V>) -> usize {
80        if let Some(idx) = self.free.pop() {
81            self.nodes[idx] = Some(node);
82            idx
83        } else {
84            self.nodes.push(Some(node));
85            self.nodes.len() - 1
86        }
87    }
88
89    /// Free `idx` for reuse and return the owned node.
90    fn dealloc(&mut self, idx: usize) -> Node<K, V> {
91        let node = self.nodes[idx]
92            .take()
93            .unwrap_or_else(|| unreachable!("arena slot must be occupied to be deallocated"));
94        self.free.push(idx);
95        node
96    }
97
98    /// Unlink `idx` from the access-order list. Caller must ensure `idx`
99    /// is currently in the list.
100    fn unlink(&mut self, idx: usize) {
101        let (prev, next) = {
102            let n = self.nodes[idx]
103                .as_ref()
104                .unwrap_or_else(|| unreachable!("unlink target must be occupied"));
105            (n.prev, n.next)
106        };
107        match prev {
108            Some(p) => {
109                self.nodes[p]
110                    .as_mut()
111                    .unwrap_or_else(|| unreachable!())
112                    .next = next
113            }
114            None => self.head = next,
115        }
116        match next {
117            Some(n) => {
118                self.nodes[n]
119                    .as_mut()
120                    .unwrap_or_else(|| unreachable!())
121                    .prev = prev
122            }
123            None => self.tail = prev,
124        }
125        if let Some(n) = self.nodes[idx].as_mut() {
126            n.prev = None;
127            n.next = None;
128        }
129    }
130
131    /// Insert `idx` at the head (most-recently-used position).
132    fn push_front(&mut self, idx: usize) {
133        let old_head = self.head;
134        if let Some(n) = self.nodes[idx].as_mut() {
135            n.prev = None;
136            n.next = old_head;
137        }
138        if let Some(h) = old_head {
139            if let Some(n) = self.nodes[h].as_mut() {
140                n.prev = Some(idx);
141            }
142        } else {
143            // First entry — both head and tail.
144            self.tail = Some(idx);
145        }
146        self.head = Some(idx);
147    }
148
149    /// Promote `idx` to head if it's not already there.
150    fn promote(&mut self, idx: usize) {
151        if self.head == Some(idx) {
152            return;
153        }
154        self.unlink(idx);
155        self.push_front(idx);
156    }
157}
158
159impl<K, V> LruCache<K, V>
160where
161    K: Eq + Hash + Clone,
162    V: Clone,
163{
164    /// Creates a cache with the given capacity.
165    ///
166    /// Returns [`CacheError::InvalidCapacity`] if `capacity == 0`.
167    ///
168    /// # Example
169    ///
170    /// ```
171    /// use cache_mod::LruCache;
172    ///
173    /// let cache: LruCache<String, u32> = LruCache::new(128).expect("capacity > 0");
174    /// ```
175    pub fn new(capacity: usize) -> Result<Self, CacheError> {
176        let cap = NonZeroUsize::new(capacity).ok_or(CacheError::InvalidCapacity)?;
177        Ok(Self::with_capacity(cap))
178    }
179
180    /// Creates a cache with the given non-zero capacity. Infallible.
181    ///
182    /// # Example
183    ///
184    /// ```
185    /// use std::num::NonZeroUsize;
186    /// use cache_mod::LruCache;
187    ///
188    /// let cap = NonZeroUsize::new(64).expect("64 != 0");
189    /// let cache: LruCache<String, u32> = LruCache::with_capacity(cap);
190    /// ```
191    pub fn with_capacity(capacity: NonZeroUsize) -> Self {
192        let cap = capacity.get();
193        Self {
194            capacity,
195            inner: Mutex::new(Inner::with_capacity(cap)),
196        }
197    }
198}
199
200impl<K, V> Cache<K, V> for LruCache<K, V>
201where
202    K: Eq + Hash + Clone,
203    V: Clone,
204{
205    fn get(&self, key: &K) -> Option<V> {
206        let mut inner = self.inner.lock_recover();
207        let idx = *inner.map.get(key)?;
208        inner.promote(idx);
209        let value = inner.nodes[idx]
210            .as_ref()
211            .map(|n| n.value.clone())
212            .unwrap_or_else(|| unreachable!("promoted node must be occupied"));
213        Some(value)
214    }
215
216    fn insert(&self, key: K, value: V) -> Option<V> {
217        let mut inner = self.inner.lock_recover();
218
219        if let Some(&idx) = inner.map.get(&key) {
220            // Live update: replace value, promote.
221            let old = inner.nodes[idx]
222                .as_mut()
223                .map(|n| core::mem::replace(&mut n.value, value))
224                .unwrap_or_else(|| unreachable!("mapped index must be occupied"));
225            inner.promote(idx);
226            return Some(old);
227        }
228
229        // New entry. Evict the LRU tail if at capacity.
230        if inner.map.len() >= self.capacity.get() {
231            if let Some(tail_idx) = inner.tail {
232                inner.unlink(tail_idx);
233                let evicted = inner.dealloc(tail_idx);
234                let _ = inner.map.remove(&evicted.key);
235            }
236        }
237
238        let idx = inner.alloc(Node {
239            key: key.clone(),
240            value,
241            prev: None,
242            next: None,
243        });
244        inner.push_front(idx);
245        let _ = inner.map.insert(key, idx);
246        None
247    }
248
249    fn remove(&self, key: &K) -> Option<V> {
250        let mut inner = self.inner.lock_recover();
251        let idx = inner.map.remove(key)?;
252        inner.unlink(idx);
253        let node = inner.dealloc(idx);
254        Some(node.value)
255    }
256
257    fn contains_key(&self, key: &K) -> bool {
258        self.inner.lock_recover().map.contains_key(key)
259    }
260
261    fn len(&self) -> usize {
262        self.inner.lock_recover().map.len()
263    }
264
265    fn clear(&self) {
266        let mut inner = self.inner.lock_recover();
267        inner.nodes.clear();
268        inner.free.clear();
269        inner.head = None;
270        inner.tail = None;
271        inner.map.clear();
272    }
273
274    fn capacity(&self) -> usize {
275        self.capacity.get()
276    }
277}