cache_rs/
lru.rs

1//! Least Recently Used (LRU) Cache Implementation
2//!
3//! This module provides a memory-efficient LRU cache implementation with O(1) operations
4//! for all common cache operations. LRU is one of the most widely used cache eviction
5//! algorithms due to its simplicity and good performance for workloads with temporal locality.
6//!
7//! # Algorithm
8//!
9//! The LRU cache maintains items in order of recency of use, evicting the least recently
10//! used item when capacity is reached. This works on the principle of temporal locality:
11//! items that have been accessed recently are likely to be accessed again soon.
12//!
13//! # Performance Characteristics
14//!
15//! - **Time Complexity**:
16//!   - Get: O(1)
17//!   - Put: O(1)
18//!   - Remove: O(1)
19//!
20//! - **Space Complexity**:
21//!   - O(n) where n is the capacity of the cache
22//!   - Memory overhead is approximately 48 bytes per entry plus the size of keys and values
23//!
24//! # When to Use
25//!
26//! LRU caches are ideal for:
27//! - General-purpose caching where access patterns exhibit temporal locality
28//! - Simple implementation with predictable performance
29//! - Caching with a fixed memory budget
30//!
31//! They are less suitable for:
32//! - Workloads where frequency of access is more important than recency
33//! - Scanning patterns where a large set of items is accessed once in sequence
34//!
35//! # Thread Safety
36//!
37//! This implementation is not thread-safe. For concurrent access, you should
38//! wrap the cache with a synchronization primitive such as `Mutex` or `RwLock`.
39
40extern crate alloc;
41
42use crate::config::LruCacheConfig;
43use crate::list::{Entry, List};
44use crate::metrics::{CacheMetrics, LruCacheMetrics};
45use alloc::boxed::Box;
46use alloc::collections::BTreeMap;
47use alloc::string::String;
48use core::borrow::Borrow;
49use core::hash::{BuildHasher, Hash};
50use core::mem;
51use core::num::NonZeroUsize;
52
53#[cfg(feature = "hashbrown")]
54use hashbrown::hash_map::DefaultHashBuilder;
55#[cfg(feature = "hashbrown")]
56use hashbrown::HashMap;
57
58#[cfg(not(feature = "hashbrown"))]
59use std::collections::hash_map::RandomState as DefaultHashBuilder;
60#[cfg(not(feature = "hashbrown"))]
61use std::collections::HashMap;
62
63/// An implementation of a Least Recently Used (LRU) cache.
64///
65/// The cache has a fixed capacity and supports O(1) operations for
66/// inserting, retrieving, and updating entries. When the cache reaches capacity,
67/// the least recently used entry will be evicted to make room for new entries.
68///
69/// # Examples
70///
71/// ```
72/// use cache_rs::LruCache;
73/// use core::num::NonZeroUsize;
74///
75/// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
76///
77/// // Add items to the cache
78/// cache.put("apple", 1);
79/// cache.put("banana", 2);
80///
81/// // Accessing items updates their recency
82/// assert_eq!(cache.get(&"apple"), Some(&1));
83///
84/// // Adding beyond capacity evicts the least recently used item
85/// // In this case "banana" is evicted because "apple" was just accessed
86/// cache.put("cherry", 3);
87/// assert_eq!(cache.get(&"banana"), None);
88/// assert_eq!(cache.get(&"apple"), Some(&1));
89/// assert_eq!(cache.get(&"cherry"), Some(&3));
90/// ```
91///
92/// # Memory Usage
93///
94/// The memory usage of this cache is approximately:
95/// - 16 bytes for the cache struct itself
96/// - (32 + size_of(K) + size_of(V)) bytes per entry
97/// - Plus HashMap overhead
98///
99/// # Examples
100///
101/// ```
102/// use cache_rs::lru::LruCache;
103/// use core::num::NonZeroUsize;
104///
105/// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
106///
107/// // Insert some items
108/// cache.put("apple", 1);
109/// cache.put("banana", 2);
110///
111/// // Most recently used is banana, then apple
112/// assert_eq!(cache.get(&"apple"), Some(&1));
113/// // Now most recently used is apple, then banana
114///
115/// // Add a new item, evicting the least recently used (banana)
116/// cache.put("cherry", 3);
117///
118/// // Banana was evicted
119/// assert_eq!(cache.get(&"banana"), None);
120/// assert_eq!(cache.get(&"apple"), Some(&1));
121/// assert_eq!(cache.get(&"cherry"), Some(&3));
122/// ```
123#[derive(Debug)]
124pub struct LruCache<K, V, S = DefaultHashBuilder> {
125    /// Configuration for the LRU cache
126    config: LruCacheConfig,
127
128    /// The internal list holding the key-value pairs in LRU order.  
129    list: List<(K, V)>,
130
131    /// A hash map mapping keys to pointers to the list entries.
132    map: HashMap<K, *mut Entry<(K, V)>, S>,
133
134    /// Metrics tracking for this cache instance.
135    metrics: LruCacheMetrics,
136}
137
138impl<K: Hash + Eq, V: Clone, S: BuildHasher> LruCache<K, V, S> {
139    /// Creates a new LRU cache that holds at most `cap` items using the specified hash builder.
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// use cache_rs::lru::LruCache;
145    /// use core::num::NonZeroUsize;
146    /// use std::collections::hash_map::RandomState;
147    ///
148    /// let cache: LruCache<&str, u32, _> = LruCache::with_hasher(
149    ///     NonZeroUsize::new(10).unwrap(),
150    ///     RandomState::new()
151    /// );
152    /// ```
153    pub fn with_hasher(cap: NonZeroUsize, hash_builder: S) -> Self {
154        // Default to a reasonable estimate: 1KB average object size
155        Self::with_hasher_and_size(cap, hash_builder, cap.get() as u64 * 1024)
156    }
157
158    /// Creates a new LRU cache with specified capacity, hash builder, and size limit in bytes.
159    pub fn with_hasher_and_size(cap: NonZeroUsize, hash_builder: S, max_size_bytes: u64) -> Self {
160        let map_capacity = cap.get().next_power_of_two();
161        let config = LruCacheConfig::new(cap);
162        LruCache {
163            config,
164            list: List::new(cap),
165            map: HashMap::with_capacity_and_hasher(map_capacity, hash_builder),
166            metrics: LruCacheMetrics::new(max_size_bytes),
167        }
168    }
169
170    /// Returns the maximum number of key-value pairs the cache can hold.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use cache_rs::lru::LruCache;
176    /// use core::num::NonZeroUsize;
177    ///
178    /// let cache: LruCache<&str, u32> = LruCache::new(NonZeroUsize::new(10).unwrap());
179    /// assert_eq!(cache.cap().get(), 10);
180    /// ```
181    pub fn cap(&self) -> NonZeroUsize {
182        self.config.capacity()
183    }
184
185    /// Returns the number of key-value pairs in the cache.
186    ///
187    /// # Examples
188    ///
189    /// ```
190    /// use cache_rs::lru::LruCache;
191    /// use core::num::NonZeroUsize;
192    ///
193    /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
194    /// assert_eq!(cache.len(), 0);
195    ///
196    /// cache.put("apple", 1);
197    /// assert_eq!(cache.len(), 1);
198    ///
199    /// cache.put("banana", 2);
200    /// assert_eq!(cache.len(), 2);
201    /// ```
202    pub fn len(&self) -> usize {
203        self.map.len()
204    }
205
206    /// Returns `true` if the cache contains no key-value pairs.
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// use cache_rs::lru::LruCache;
212    /// use core::num::NonZeroUsize;
213    ///
214    /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
215    /// assert!(cache.is_empty());
216    ///
217    /// cache.put("apple", 1);
218    /// assert!(!cache.is_empty());
219    /// ```
220    pub fn is_empty(&self) -> bool {
221        self.map.is_empty()
222    }
223
224    /// Estimates the size of a key-value pair in bytes for metrics tracking
225    fn estimate_object_size(&self, _key: &K, _value: &V) -> u64 {
226        // Simple estimation: key size + value size + overhead for pointers and metadata
227        mem::size_of::<K>() as u64 + mem::size_of::<V>() as u64 + 64
228    }
229
230    /// Returns a reference to the value corresponding to the key.
231    ///
232    /// The key may be any borrowed form of the cache's key type, but
233    /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
234    /// the key type.
235    ///
236    /// If a value is returned, that key-value pair becomes the most recently used pair in the cache.
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// use cache_rs::lru::LruCache;
242    /// use core::num::NonZeroUsize;
243    ///
244    /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
245    /// cache.put("apple", 1);
246    /// cache.put("banana", 2);
247    ///
248    /// assert_eq!(cache.get(&"apple"), Some(&1));
249    /// assert_eq!(cache.get(&"banana"), Some(&2));
250    /// assert_eq!(cache.get(&"cherry"), None);
251    /// ```
252    pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
253    where
254        K: Borrow<Q>,
255        Q: ?Sized + Hash + Eq,
256    {
257        if let Some(node) = self.map.get(key).copied() {
258            // Cache hit
259            unsafe {
260                // SAFETY: node comes from our map, so it's a valid pointer to an entry in our list
261                self.list.move_to_front(node);
262                let (k, v) = (*node).get_value();
263
264                // Record hit with estimated object size
265                let object_size = self.estimate_object_size(k, v);
266                self.metrics.core.record_hit(object_size);
267
268                Some(v)
269            }
270        } else {
271            // Cache miss - we can't estimate size without the actual object
272            // The simulation system will need to call record_miss separately
273            None
274        }
275    }
276
277    /// Records a cache miss for metrics tracking (to be called by simulation system)
278    pub fn record_miss(&mut self, object_size: u64) {
279        self.metrics.core.record_miss(object_size);
280    }
281
282    /// Returns a mutable reference to the value corresponding to the key.
283    ///
284    /// The key may be any borrowed form of the cache's key type, but
285    /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
286    /// the key type.
287    ///
288    /// If a value is returned, that key-value pair becomes the most recently used pair in the cache.
289    ///
290    /// # Examples
291    ///
292    /// ```
293    /// use cache_rs::lru::LruCache;
294    /// use core::num::NonZeroUsize;
295    ///
296    /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
297    /// cache.put("apple", 1);
298    ///
299    /// if let Some(v) = cache.get_mut(&"apple") {
300    ///     *v = 4;
301    /// }
302    ///
303    /// assert_eq!(cache.get(&"apple"), Some(&4));
304    /// ```
305    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
306    where
307        K: Borrow<Q>,
308        Q: ?Sized + Hash + Eq,
309    {
310        let node = self.map.get(key).copied()?;
311
312        // Move the node to the front of the list to mark it as most recently used
313        // SAFETY: node comes from our map, so it's a valid pointer to an entry in our list
314        unsafe {
315            self.list.move_to_front(node);
316            let (k, v) = (*node).get_value_mut();
317
318            // Record hit with estimated object size
319            let object_size = self.estimate_object_size(k, v);
320            self.metrics.core.record_hit(object_size);
321
322            Some(v)
323        }
324    }
325}
326
327impl<K: Hash + Eq + Clone, V, S: BuildHasher> LruCache<K, V, S> {
328    /// Inserts a key-value pair into the cache.
329    ///
330    /// If the cache already contained this key, the old value is replaced and returned.
331    /// Otherwise, if the cache is at capacity, the least recently used
332    /// key-value pair will be evicted and returned.
333    ///
334    /// The inserted key-value pair becomes the most recently used pair in the cache.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use cache_rs::lru::LruCache;
340    /// use core::num::NonZeroUsize;
341    ///
342    /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
343    ///
344    /// assert_eq!(cache.put("apple", 1), None); // No previous value
345    /// assert_eq!(cache.put("apple", 4).unwrap().1, 1); // Replaced existing value
346    ///
347    /// // At capacity, adding new item evicts least recently used
348    /// cache.put("banana", 2);
349    /// assert_eq!(cache.put("cherry", 3).unwrap().1, 4); // Evicted "apple"
350    /// assert_eq!(cache.get(&"apple"), None); // "apple" was evicted
351    /// ```
352    pub fn put(&mut self, key: K, value: V) -> Option<(K, V)>
353    where
354        V: Clone,
355    {
356        let mut evicted = None;
357        let object_size = self.estimate_object_size(&key, &value);
358
359        // If key is already in the cache
360        if let Some(&node) = self.map.get(&key) {
361            // SAFETY: node comes from our map, so it's a valid pointer to an entry in our list
362            unsafe {
363                self.list.move_to_front(node);
364                let (k, old_value) = self.list.update(node, (key, value), true).0?;
365                // This was an update, not a new insertion, so just track the size change
366                return Some((k, old_value));
367            }
368        }
369
370        // If we're at capacity, remove the least recently used item from the cache
371        if self.map.len() >= self.cap().get() {
372            if let Some(old_entry) = self.list.remove_last() {
373                // Extract the key and remove it from the map
374                unsafe {
375                    // SAFETY: old_entry comes from list.remove_last(), so it's a valid Box
376                    // that we own. Converting to raw pointer is safe for temporary access.
377                    let entry_ptr = Box::into_raw(old_entry);
378                    let key_ref = &(*entry_ptr).get_value().0;
379                    self.map.remove(key_ref);
380                    let key = (*entry_ptr).get_value().0.clone();
381                    let value = (*entry_ptr).get_value().1.clone();
382
383                    // Record eviction
384                    let evicted_size = self.estimate_object_size(&key, &value);
385                    self.metrics.core.record_eviction(evicted_size);
386
387                    evicted = Some((key, value));
388                    // SAFETY: Reconstructing Box from the same raw pointer to properly free memory
389                    let _ = Box::from_raw(entry_ptr);
390                }
391            }
392        }
393
394        // Insert the new key-value pair at the front of the list
395        if let Some(node) = self.list.add((key.clone(), value)) {
396            // SAFETY: node comes from our list.add() call, so it's a valid pointer
397            self.map.insert(key, node);
398
399            // Record the insertion (increase in stored data)
400            self.metrics.core.record_insertion(object_size);
401        }
402
403        evicted
404    }
405
406    /// Removes a key from the cache, returning the value at the key if the key was previously in the cache.
407    ///
408    /// The key may be any borrowed form of the cache's key type, but
409    /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
410    /// the key type.
411    ///
412    /// # Examples
413    ///
414    /// ```
415    /// use cache_rs::lru::LruCache;
416    /// use core::num::NonZeroUsize;
417    ///
418    /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
419    /// cache.put("apple", 1);
420    ///
421    /// assert_eq!(cache.remove(&"apple"), Some(1));
422    /// assert_eq!(cache.remove(&"apple"), None);
423    /// ```
424    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
425    where
426        K: Borrow<Q>,
427        Q: ?Sized + Hash + Eq,
428        V: Clone,
429    {
430        let node = self.map.remove(key)?;
431
432        unsafe {
433            // SAFETY: node comes from our map and was just removed, so it's a valid pointer to an entry in our list
434            // Get the key-value pair before removing
435            let (k, v) = (*node).get_value();
436            let object_size = self.estimate_object_size(k, v);
437            let value = v.clone();
438
439            // Detach the node from the list
440            self.list.remove(node);
441
442            // Record the removal (decrease in stored data)
443            self.metrics.core.record_eviction(object_size);
444
445            // Return the value (second element) from the tuple
446            Some(value)
447        }
448    }
449
450    /// Clears the cache, removing all key-value pairs.
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use cache_rs::lru::LruCache;
456    /// use core::num::NonZeroUsize;
457    ///
458    /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
459    /// cache.put("apple", 1);
460    /// cache.put("banana", 2);
461    ///
462    /// assert!(!cache.is_empty());
463    /// cache.clear();
464    /// assert!(cache.is_empty());
465    /// ```
466    pub fn clear(&mut self) {
467        // Reset cache size to zero
468        self.metrics.core.cache_size_bytes = 0;
469
470        self.map.clear();
471        self.list.clear();
472    }
473
474    /// Returns an iterator over the cache's key-value pairs in least-recently-used to most-recently-used order.
475    ///
476    /// # Examples
477    ///
478    /// ```ignore
479    /// use cache_rs::lru::LruCache;
480    /// use core::num::NonZeroUsize;
481    /// use std::prelude::v1::*;
482    ///
483    /// let mut cache = LruCache::new(NonZeroUsize::new(3).unwrap());
484    /// cache.put("a", 1);
485    /// cache.put("b", 2);
486    /// cache.put("c", 3);
487    ///
488    /// // Access "a" to move it to the front
489    /// assert_eq!(cache.get(&"a"), Some(&1));
490    ///
491    /// let items: Vec<_> = cache.iter().collect();
492    /// assert_eq!(items, [(&"b", &2), (&"c", &3), (&"a", &1)]);
493    /// ```
494    pub fn iter(&self) -> Iter<'_, K, V> {
495        // Not implemented here - this requires a more complex implementation to traverse
496        // the linked list in reverse order
497        unimplemented!("Iteration not yet implemented")
498    }
499
500    /// Returns a mutable iterator over the cache's key-value pairs in least-recently-used to most-recently-used order.
501    ///
502    /// # Examples
503    ///
504    /// ```ignore
505    /// use cache_rs::lru::LruCache;
506    /// use core::num::NonZeroUsize;
507    /// use std::prelude::v1::*;
508    ///
509    /// let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
510    /// cache.put("a", 1);
511    /// cache.put("b", 2);
512    ///
513    /// for (_, v) in cache.iter_mut() {
514    ///     *v += 10;
515    /// }
516    ///
517    /// assert_eq!(cache.get(&"a"), Some(&11));
518    /// assert_eq!(cache.get(&"b"), Some(&12));
519    /// ```
520    pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
521        // Not implemented here - this requires a more complex implementation to traverse
522        // the linked list in reverse order
523        unimplemented!("Mutable iteration not yet implemented")
524    }
525}
526
527impl<K: Hash + Eq, V> LruCache<K, V>
528where
529    V: Clone,
530{
531    /// Creates a new LRU cache that holds at most `cap` items.
532    ///
533    /// # Examples
534    ///
535    /// ```
536    /// use cache_rs::lru::LruCache;
537    /// use core::num::NonZeroUsize;
538    ///
539    /// let cache: LruCache<&str, u32> = LruCache::new(NonZeroUsize::new(10).unwrap());
540    /// ```
541    pub fn new(cap: NonZeroUsize) -> LruCache<K, V, DefaultHashBuilder> {
542        LruCache::with_hasher(cap, DefaultHashBuilder::default())
543    }
544}
545
546impl<K: Hash + Eq, V, S: BuildHasher> CacheMetrics for LruCache<K, V, S> {
547    fn metrics(&self) -> BTreeMap<String, f64> {
548        self.metrics.metrics()
549    }
550
551    fn algorithm_name(&self) -> &'static str {
552        self.metrics.algorithm_name()
553    }
554}
555
556/// An iterator over the entries of a `LruCache` in least-recently-used to most-recently-used order.
557///
558/// This struct is created by the [`LruCache::iter`] method.
559pub struct Iter<'a, K, V> {
560    _marker: core::marker::PhantomData<&'a (K, V)>,
561}
562
563/// A mutable iterator over the entries of a `LruCache` in least-recently-used to most-recently-used order.
564///
565/// This struct is created by the [`LruCache::iter_mut`] method.
566pub struct IterMut<'a, K, V> {
567    _marker: core::marker::PhantomData<&'a mut (K, V)>,
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573    use alloc::string::String;
574
575    #[test]
576    fn test_lru_get_put() {
577        let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
578
579        // Insert items
580        assert_eq!(cache.put("apple", 1), None);
581        assert_eq!(cache.put("banana", 2), None);
582
583        // Get existing items
584        assert_eq!(cache.get(&"apple"), Some(&1));
585        assert_eq!(cache.get(&"banana"), Some(&2));
586
587        // Get non-existent item
588        assert_eq!(cache.get(&"cherry"), None);
589
590        // Replace existing item
591        assert_eq!(cache.put("apple", 3).unwrap().1, 1);
592        assert_eq!(cache.get(&"apple"), Some(&3));
593
594        // Add a third item, should evict the least recently used (banana)
595        assert_eq!(cache.put("cherry", 4).unwrap().1, 2);
596
597        // Banana should be gone, apple and cherry should exist
598        assert_eq!(cache.get(&"banana"), None);
599        assert_eq!(cache.get(&"apple"), Some(&3));
600        assert_eq!(cache.get(&"cherry"), Some(&4));
601    }
602
603    #[test]
604    fn test_lru_get_mut() {
605        let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
606
607        cache.put("apple", 1);
608        cache.put("banana", 2);
609
610        // Modify value via get_mut
611        if let Some(v) = cache.get_mut(&"apple") {
612            *v = 3;
613        }
614
615        assert_eq!(cache.get(&"apple"), Some(&3));
616
617        // Add a third item, should evict the least recently used (banana)
618        // because apple was accessed most recently via get_mut
619        cache.put("cherry", 4);
620
621        assert_eq!(cache.get(&"banana"), None);
622        assert_eq!(cache.get(&"apple"), Some(&3));
623        assert_eq!(cache.get(&"cherry"), Some(&4));
624    }
625
626    #[test]
627    fn test_lru_remove() {
628        let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
629
630        cache.put("apple", 1);
631        cache.put("banana", 2);
632
633        // Get existing items
634        assert_eq!(cache.get(&"apple"), Some(&1));
635        assert_eq!(cache.get(&"banana"), Some(&2));
636        assert_eq!(cache.get(&"cherry"), None);
637
638        // Remove item
639        assert_eq!(cache.remove(&"apple"), Some(1));
640        assert_eq!(cache.get(&"apple"), None);
641        assert_eq!(cache.len(), 1);
642
643        // Remove non-existent item
644        assert_eq!(cache.remove(&"cherry"), None);
645
646        // We should be able to add a new item now
647        let evicted = cache.put("cherry", 3);
648        assert_eq!(evicted, None);
649        assert_eq!(cache.get(&"banana"), Some(&2));
650        assert_eq!(cache.get(&"cherry"), Some(&3));
651    }
652
653    #[test]
654    fn test_lru_clear() {
655        let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
656
657        cache.put("apple", 1);
658        cache.put("banana", 2);
659
660        assert_eq!(cache.len(), 2);
661        cache.clear();
662        assert_eq!(cache.len(), 0);
663        assert!(cache.is_empty());
664
665        // Should be able to add new items after clearing
666        cache.put("cherry", 3);
667        assert_eq!(cache.get(&"cherry"), Some(&3));
668    }
669
670    #[test]
671    fn test_lru_capacity_limits() {
672        let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
673
674        cache.put("apple", 1);
675        cache.put("banana", 2);
676        cache.put("cherry", 3);
677
678        // Should only have the 2 most recently used items
679        assert_eq!(cache.len(), 2);
680        assert_eq!(cache.get(&"apple"), None); // evicted
681        assert_eq!(cache.get(&"banana"), Some(&2));
682        assert_eq!(cache.get(&"cherry"), Some(&3));
683    }
684
685    #[test]
686    fn test_lru_string_keys() {
687        let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
688
689        let key1 = String::from("apple");
690        let key2 = String::from("banana");
691
692        cache.put(key1.clone(), 1);
693        cache.put(key2.clone(), 2);
694
695        assert_eq!(cache.get(&key1), Some(&1));
696        assert_eq!(cache.get(&key2), Some(&2));
697
698        // String slice lookup should work too
699        assert_eq!(cache.get("apple"), Some(&1));
700        assert_eq!(cache.get("banana"), Some(&2));
701    }
702
703    #[derive(Debug, Clone, Eq, PartialEq)]
704    struct ComplexValue {
705        val: i32,
706        description: String,
707    }
708
709    #[test]
710    fn test_lru_complex_values() {
711        let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
712
713        let key1 = String::from("apple");
714        let key2 = String::from("banana");
715
716        let fruit1 = ComplexValue {
717            val: 1,
718            description: String::from("First fruit"),
719        };
720        let fruit2 = ComplexValue {
721            val: 2,
722            description: String::from("Second fruit"),
723        };
724        let fruit3 = ComplexValue {
725            val: 3,
726            description: String::from("Third fruit"),
727        };
728
729        cache.put(key1.clone(), fruit1.clone());
730        cache.put(key2.clone(), fruit2.clone());
731
732        assert_eq!(cache.get(&key1).unwrap().val, fruit1.val);
733        assert_eq!(cache.get(&key2).unwrap().val, fruit2.val);
734
735        // Lets add another item
736        let evicted = cache.put(String::from("cherry"), fruit3.clone());
737        let evicted_fruit = evicted.unwrap();
738        assert_eq!(evicted_fruit.1, fruit1);
739
740        // lets remove the first item
741        let removed = cache.remove(&key1);
742        assert_eq!(removed, None);
743    }
744
745    #[test]
746    fn test_lru_metrics() {
747        use crate::metrics::CacheMetrics;
748
749        let mut cache = LruCache::new(NonZeroUsize::new(2).unwrap());
750
751        // Test initial metrics
752        let metrics = cache.metrics();
753        assert_eq!(metrics.get("requests").unwrap(), &0.0);
754        assert_eq!(metrics.get("cache_hits").unwrap(), &0.0);
755        assert_eq!(metrics.get("cache_misses").unwrap(), &0.0);
756
757        // Add some items
758        cache.put("apple", 1);
759        cache.put("banana", 2);
760
761        // Test cache hits
762        cache.get(&"apple");
763        cache.get(&"banana");
764
765        let metrics = cache.metrics();
766        assert_eq!(metrics.get("cache_hits").unwrap(), &2.0);
767
768        // Test cache miss
769        cache.record_miss(64); // Simulate a miss
770        let metrics = cache.metrics();
771        assert_eq!(metrics.get("cache_misses").unwrap(), &1.0);
772        assert_eq!(metrics.get("requests").unwrap(), &3.0);
773
774        // Test eviction by adding a third item
775        cache.put("cherry", 3);
776        let metrics = cache.metrics();
777        assert_eq!(metrics.get("evictions").unwrap(), &1.0);
778
779        // Test bytes_written_to_cache tracking
780        assert!(metrics.get("bytes_written_to_cache").unwrap() > &0.0);
781
782        // Test algorithm name
783        assert_eq!(cache.algorithm_name(), "LRU");
784    }
785}