1use alloy_primitives::map::DefaultHashBuilder;
4use core::hash::BuildHasher;
5use derive_more::{Deref, DerefMut};
6use itertools::Itertools;
7use schnellru::{ByLength, Limiter, Unlimited};
8use std::{fmt, hash::Hash};
9
10pub struct LruCache<T: Hash + Eq + fmt::Debug, S = DefaultHashBuilder>
15where
16 S: BuildHasher,
17{
18 limit: u32,
19 inner: LruMap<T, (), ByLength, S>,
20}
21
22impl<T: Hash + Eq + fmt::Debug> LruCache<T> {
23 pub fn new(limit: u32) -> Self {
25 Self::with_hasher(limit, Default::default())
26 }
27}
28
29impl<T: Hash + Eq + fmt::Debug, S: BuildHasher> LruCache<T, S> {
30 pub const fn with_hasher(limit: u32, hash_builder: S) -> Self {
32 Self { inner: LruMap::with_hasher(limit + 1, hash_builder), limit }
35 }
36
37 pub fn insert(&mut self, entry: T) -> bool {
49 let mut is_new = false;
50 self.inner.get_or_insert(entry, || {
51 is_new = true;
52 });
53 is_new
54 }
55
56 pub fn insert_and_get_evicted(&mut self, entry: T) -> (bool, Option<T>) {
59 let new = self.inner.peek(&entry).is_none();
60 let evicted =
61 (new && (self.limit as usize) <= self.inner.len()).then(|| self.remove_lru()).flatten();
62 _ = self.inner.get_or_insert(entry, || ());
63
64 (new, evicted)
65 }
66
67 pub fn get(&mut self, entry: &T) -> Option<&T> {
69 let _ = self.inner.get(entry)?;
70 self.iter().next()
71 }
72
73 pub fn find(&self, entry: &T) -> Option<&T> {
80 self.iter().find(|key| *key == entry)
81 }
82
83 #[inline]
88 fn remove_lru(&mut self) -> Option<T> {
89 self.inner.pop_oldest().map(|(k, ())| k)
90 }
91
92 pub fn remove(&mut self, value: &T) -> bool {
94 self.inner.remove(value).is_some()
95 }
96
97 pub fn contains(&self, value: &T) -> bool {
99 self.inner.peek(value).is_some()
100 }
101
102 pub fn iter(&self) -> impl Iterator<Item = &T> + '_ {
104 self.inner.iter().map(|(k, ())| k)
105 }
106
107 pub fn len(&self) -> usize {
109 self.inner.len()
110 }
111
112 pub fn is_empty(&self) -> bool {
114 self.inner.is_empty()
115 }
116}
117
118impl<T, S> Extend<T> for LruCache<T, S>
119where
120 T: Eq + Hash + fmt::Debug,
121 S: BuildHasher,
122{
123 fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
124 for item in iter {
125 _ = self.insert(item);
126 }
127 }
128}
129
130impl<T, S> fmt::Debug for LruCache<T, S>
131where
132 T: fmt::Debug + Hash + Eq,
133 S: BuildHasher,
134{
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 let mut debug_struct = f.debug_struct("LruCache");
137
138 debug_struct.field("limit", &self.limit);
139
140 debug_struct.field(
141 "ret %iter",
142 &format_args!("Iter: {{{} }}", self.iter().map(|k| format!(" {k:?}")).format(",")),
143 );
144
145 debug_struct.finish()
146 }
147}
148
149#[derive(Deref, DerefMut, Default)]
152pub struct LruMap<K, V, L = ByLength, S = DefaultHashBuilder>(schnellru::LruMap<K, V, L, S>)
153where
154 K: Hash + PartialEq,
155 L: Limiter<K, V>,
156 S: BuildHasher;
157
158impl<K, V, L, S> fmt::Debug for LruMap<K, V, L, S>
159where
160 K: Hash + PartialEq + fmt::Display,
161 V: fmt::Debug,
162 L: Limiter<K, V> + fmt::Debug,
163 S: BuildHasher,
164{
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 let mut debug_struct = f.debug_struct("LruMap");
167
168 debug_struct.field("limiter", self.limiter());
169
170 debug_struct.field(
171 "ret %iter",
172 &format_args!(
173 "Iter: {{{} }}",
174 self.iter().map(|(k, v)| format!(" {k}: {v:?}")).format(",")
175 ),
176 );
177
178 debug_struct.finish()
179 }
180}
181
182impl<K, V> LruMap<K, V>
183where
184 K: Hash + PartialEq,
185{
186 pub fn new(max_length: u32) -> Self {
188 Self::with_hasher(max_length, Default::default())
189 }
190}
191
192impl<K, V, S> LruMap<K, V, ByLength, S>
193where
194 K: Hash + PartialEq,
195 S: BuildHasher,
196{
197 pub const fn with_hasher(max_length: u32, hash_builder: S) -> Self {
199 Self(schnellru::LruMap::with_hasher(ByLength::new(max_length), hash_builder))
200 }
201}
202
203impl<K, V> LruMap<K, V, Unlimited>
204where
205 K: Hash + PartialEq,
206{
207 pub fn new_unlimited() -> Self {
209 Self(schnellru::LruMap::with_hasher(Unlimited, Default::default()))
210 }
211}
212
213#[cfg(test)]
214mod test {
215 use super::*;
216 use derive_more::{Constructor, Display};
217 use std::hash::Hasher;
218
219 #[derive(Debug, Hash, PartialEq, Eq, Display, Clone, Copy)]
220 struct Key(i8);
221
222 #[derive(Debug, Eq, Constructor, Clone, Copy)]
223 struct CompoundKey {
224 id: i8,
226 other: i8,
227 }
228
229 impl PartialEq for CompoundKey {
230 fn eq(&self, other: &Self) -> bool {
231 self.id == other.id
232 }
233 }
234
235 impl Hash for CompoundKey {
236 fn hash<H: Hasher>(&self, state: &mut H) {
237 self.id.hash(state)
238 }
239 }
240
241 #[test]
242 fn test_cache_should_insert_into_empty_set() {
243 let mut cache = LruCache::new(5);
244 let entry = "entry";
245 assert!(cache.insert(entry));
246 assert!(cache.contains(&entry));
247 }
248
249 #[test]
250 fn test_cache_should_not_insert_same_value_twice() {
251 let mut cache = LruCache::new(5);
252 let entry = "entry";
253 assert!(cache.insert(entry));
254 assert!(!cache.insert(entry));
255 }
256
257 #[test]
258 fn test_cache_should_remove_oldest_element_when_exceeding_limit() {
259 let mut cache = LruCache::new(1); let old_entry = "old_entry";
261 let new_entry = "new_entry";
262 cache.insert(old_entry);
263 cache.insert("entry");
264 cache.insert(new_entry);
265 assert!(cache.contains(&new_entry));
266 assert!(!cache.contains(&old_entry));
267 }
268
269 #[test]
270 fn test_cache_should_extend_an_array() {
271 let mut cache = LruCache::new(5);
272 let entries = ["some_entry", "another_entry"];
273 cache.extend(entries);
274 for e in entries {
275 assert!(cache.contains(&e));
276 }
277 }
278
279 #[test]
280 #[expect(dead_code)]
281 fn test_debug_impl_lru_map() {
282 #[derive(Debug)]
283 struct Value(i8);
284
285 let mut cache = LruMap::new(2);
286 let key_1 = Key(1);
287 let value_1 = Value(11);
288 cache.insert(key_1, value_1);
289 let key_2 = Key(2);
290 let value_2 = Value(22);
291 cache.insert(key_2, value_2);
292
293 assert_eq!(
294 "LruMap { limiter: ByLength { max_length: 2 }, ret %iter: Iter: { 2: Value(22), 1: Value(11) } }",
295 format!("{cache:?}")
296 )
297 }
298
299 #[test]
300 fn test_debug_impl_lru_cache() {
301 let mut cache = LruCache::new(2);
302 let key_1 = Key(1);
303 cache.insert(key_1);
304 let key_2 = Key(2);
305 cache.insert(key_2);
306
307 assert_eq!(
308 "LruCache { limit: 2, ret %iter: Iter: { Key(2), Key(1) } }",
309 format!("{cache:?}")
310 )
311 }
312
313 #[test]
314 fn get() {
315 let mut cache = LruCache::new(2);
316 let key_1 = Key(1);
317 cache.insert(key_1);
318 let key_2 = Key(2);
319 cache.insert(key_2);
320
321 _ = cache.get(&key_1);
323
324 assert_eq!(
325 "LruCache { limit: 2, ret %iter: Iter: { Key(1), Key(2) } }",
326 format!("{cache:?}")
327 )
328 }
329
330 #[test]
331 fn get_ty_custom_eq_impl() {
332 let mut cache = LruCache::new(2);
333 let key_1 = CompoundKey::new(1, 11);
334 cache.insert(key_1);
335 let key_2 = CompoundKey::new(2, 22);
336 cache.insert(key_2);
337
338 let key = cache.get(&key_1);
339
340 assert_eq!(key_1.other, key.unwrap().other)
341 }
342
343 #[test]
344 fn peek() {
345 let mut cache = LruCache::new(2);
346 let key_1 = Key(1);
347 cache.insert(key_1);
348 let key_2 = Key(2);
349 cache.insert(key_2);
350
351 _ = cache.find(&key_1);
353
354 assert_eq!(
355 "LruCache { limit: 2, ret %iter: Iter: { Key(2), Key(1) } }",
356 format!("{cache:?}")
357 )
358 }
359
360 #[test]
361 fn peek_ty_custom_eq_impl() {
362 let mut cache = LruCache::new(2);
363 let key_1 = CompoundKey::new(1, 11);
364 cache.insert(key_1);
365 let key_2 = CompoundKey::new(2, 22);
366 cache.insert(key_2);
367
368 let key = cache.find(&key_1);
369
370 assert_eq!(key_1.other, key.unwrap().other)
371 }
372
373 #[test]
374 fn test_insert_methods() {
375 let mut cache = LruCache::new(2);
376
377 assert!(cache.insert("first")); assert!(!cache.insert("first")); assert!(cache.insert("second")); let (is_new, evicted) = cache.insert_and_get_evicted("third");
384 assert!(is_new); assert_eq!(evicted, Some("first")); assert!(cache.contains(&"second"));
388 assert!(cache.contains(&"third"));
389 assert!(!cache.contains(&"first"));
390
391 let (is_new, evicted) = cache.insert_and_get_evicted("second");
393 assert!(!is_new); assert_eq!(evicted, None); }
396}