1use lru::LruCache;
2use std::num::NonZeroUsize;
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::Mutex;
5
6pub struct Cache {
12 inner: Mutex<LruCache<String, i32>>,
13 capacity: usize,
14 hits: AtomicU64,
15 misses: AtomicU64,
16 evictions: AtomicU64,
17}
18
19impl Cache {
20 pub fn new(capacity: usize) -> Self {
26 let safe_capacity = capacity.max(1);
28 Self {
29 inner: Mutex::new(LruCache::new(
30 NonZeroUsize::new(safe_capacity).expect("capacity should be non-zero after max(1)"),
31 )),
32 capacity: safe_capacity,
33 hits: AtomicU64::new(0),
34 misses: AtomicU64::new(0),
35 evictions: AtomicU64::new(0),
36 }
37 }
38
39 pub fn get(&self, key: &str) -> Option<i32> {
50 let mut cache = self.inner.lock().ok()?;
52 let result = cache.get(key).copied();
53
54 if result.is_some() {
55 self.hits.fetch_add(1, Ordering::Relaxed);
56 } else {
57 self.misses.fetch_add(1, Ordering::Relaxed);
58 }
59
60 result
61 }
62
63 pub fn put(&self, key: String, value: i32) {
72 if let Ok(mut cache) = self.inner.lock() {
74 if cache.len() >= cache.cap().get() && !cache.contains(&key) {
76 self.evictions.fetch_add(1, Ordering::Relaxed);
77 }
78 cache.put(key, value);
79 }
80 }
81
82 pub fn hits(&self) -> u64 {
88 self.hits.load(Ordering::Relaxed)
89 }
90
91 pub fn misses(&self) -> u64 {
97 self.misses.load(Ordering::Relaxed)
98 }
99
100 pub fn hit_rate(&self) -> f64 {
107 let hits = self.hits();
108 let misses = self.misses();
109 let total = hits + misses;
110
111 if total == 0 {
112 0.0
113 } else {
114 (hits as f64 / total as f64) * 100.0
115 }
116 }
117
118 pub fn size(&self) -> usize {
125 self.inner.lock().ok().map(|cache| cache.len()).unwrap_or(0)
126 }
127
128 pub fn capacity(&self) -> usize {
134 self.capacity
135 }
136
137 pub fn evictions(&self) -> u64 {
143 self.evictions.load(Ordering::Relaxed)
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_cache_zero_capacity_does_not_panic() {
153 let cache = Cache::new(0);
156 cache.put("test".to_string(), 42);
157 assert_eq!(cache.get("test"), Some(42));
158 }
159
160 #[test]
161 fn test_cache_basic_operations() {
162 let cache = Cache::new(10);
164
165 assert_eq!(cache.get("key1"), None);
166
167 cache.put("key1".to_string(), 100);
168 assert_eq!(cache.get("key1"), Some(100));
169
170 cache.put("key2".to_string(), 200);
171 assert_eq!(cache.get("key2"), Some(200));
172 assert_eq!(cache.get("key1"), Some(100));
173 }
174
175 #[test]
176 fn test_cache_lru_eviction() {
177 let cache = Cache::new(2);
179
180 cache.put("key1".to_string(), 1);
181 cache.put("key2".to_string(), 2);
182 cache.put("key3".to_string(), 3); assert_eq!(cache.get("key1"), None); assert_eq!(cache.get("key2"), Some(2));
186 assert_eq!(cache.get("key3"), Some(3));
187 }
188
189 #[test]
190 fn test_cache_hit_miss_tracking() {
191 let cache = Cache::new(10);
193
194 assert_eq!(cache.hits(), 0);
196 assert_eq!(cache.misses(), 0);
197 assert_eq!(cache.hit_rate(), 0.0);
198
199 assert_eq!(cache.get("key1"), None);
201 assert_eq!(cache.hits(), 0);
202 assert_eq!(cache.misses(), 1);
203 assert_eq!(cache.hit_rate(), 0.0);
204
205 cache.put("key1".to_string(), 100);
207
208 assert_eq!(cache.get("key1"), Some(100));
210 assert_eq!(cache.hits(), 1);
211 assert_eq!(cache.misses(), 1);
212 assert_eq!(cache.hit_rate(), 50.0);
213
214 assert_eq!(cache.get("key1"), Some(100));
216 assert_eq!(cache.hits(), 2);
217 assert_eq!(cache.misses(), 1);
218 assert!((cache.hit_rate() - 66.666).abs() < 0.01);
219
220 assert_eq!(cache.get("key2"), None);
222 assert_eq!(cache.hits(), 2);
223 assert_eq!(cache.misses(), 2);
224 assert_eq!(cache.hit_rate(), 50.0);
225 }
226
227 #[test]
228 fn test_cache_eviction_tracking() {
229 let cache = Cache::new(2);
231
232 assert_eq!(cache.evictions(), 0);
233 assert_eq!(cache.size(), 0);
234 assert_eq!(cache.capacity(), 2);
235
236 cache.put("key1".to_string(), 1);
237 assert_eq!(cache.size(), 1);
238 assert_eq!(cache.evictions(), 0);
239
240 cache.put("key2".to_string(), 2);
241 assert_eq!(cache.size(), 2);
242 assert_eq!(cache.evictions(), 0);
243
244 cache.put("key3".to_string(), 3);
246 assert_eq!(cache.size(), 2);
247 assert_eq!(cache.evictions(), 1);
248
249 cache.put("key4".to_string(), 4);
251 assert_eq!(cache.size(), 2);
252 assert_eq!(cache.evictions(), 2);
253
254 cache.put("key3".to_string(), 30);
256 assert_eq!(cache.size(), 2);
257 assert_eq!(cache.evictions(), 2);
258 }
259}