sieve_cache/
lib.rs

1#![doc = include_str!("../README.md")]
2
3//! # SIEVE Cache for Rust
4//!
5//! This crate provides implementations of the SIEVE cache replacement algorithm:
6//!
7//! * [`SieveCache`] - The core single-threaded implementation (always available)
8
9#[cfg(feature = "sync")]
10pub mod _docs_sync {}
11
12#[cfg(feature = "sharded")]
13pub mod _docs_sharded {}
14
15pub mod _sieve_algorithm {
16    //! ## The SIEVE Algorithm
17    //!
18    //! SIEVE (Simple, space-efficient, In-memory, EViction mEchanism) is a cache eviction
19    //! algorithm that maintains a single bit per entry to track whether an item has been
20    //! "visited" since it was last considered for eviction. This approach requires less
21    //! state than LRU but achieves excellent performance, especially on skewed workloads.
22}
23
24pub mod _cache_implementation {
25    //! ## Cache Implementation Details
26    //!
27    //! The cache is implemented as a combination of:
28    //!
29    //! 1. A `HashMap` for O(1) key lookups
30    //! 2. A vector-based ordered collection for managing the eviction order
31    //! 3. A "visited" flag on each entry to track recent access
32    //!
33    //! When the cache is full and a new item is inserted, the eviction algorithm:
34    //! 1. Starts from the "hand" position (eviction candidate)
35    //! 2. Finds the first non-visited entry, evicting it
36    //! 3. Marks all visited entries as non-visited while searching
37}
38
39pub mod _implementation_choice {
40    //! ## Choosing the Right Implementation
41    //!
42    //! - Use [`SieveCache`] for single-threaded applications
43}
44
45#[cfg(feature = "sync")]
46pub mod _docs_sync_usage {
47    //! - Use [`SyncSieveCache`] for multi-threaded applications with moderate concurrency
48}
49
50#[cfg(feature = "sharded")]
51pub mod _docs_sharded_usage {
52    //! - Use [`ShardedSieveCache`] for applications with high concurrency where operations
53    //!   are distributed across many different keys
54}
55
56use std::borrow::Borrow;
57use std::collections::HashMap;
58use std::hash::Hash;
59
60#[cfg(feature = "sharded")]
61mod sharded;
62#[cfg(feature = "sync")]
63mod sync;
64
65#[cfg(feature = "sharded")]
66pub use sharded::ShardedSieveCache;
67#[cfg(feature = "sync")]
68pub use sync::SyncSieveCache;
69
70/// Internal representation of a cache entry
71#[derive(Clone)]
72struct Node<K: Eq + Hash + Clone, V> {
73    key: K,
74    value: V,
75    visited: bool,
76}
77
78impl<K: Eq + Hash + Clone, V> Node<K, V> {
79    fn new(key: K, value: V) -> Self {
80        Self {
81            key,
82            value,
83            visited: false,
84        }
85    }
86}
87
88/// A cache based on the SIEVE eviction algorithm.
89///
90/// `SieveCache` provides an efficient in-memory cache with a carefully designed eviction
91/// strategy that balances simplicity with good performance characteristics, especially on
92/// skewed workloads common in real-world applications.
93///
94/// This is the single-threaded implementation.
95#[cfg(feature = "sync")]
96/// For thread-safe variants, see [`SyncSieveCache`] (with the `sync` feature)
97#[cfg(feature = "sharded")]
98/// and [`ShardedSieveCache`] (with the `sharded` feature).
99///
100/// # Type Parameters
101///
102/// * `K` - The key type, which must implement `Eq`, `Hash`, and `Clone`
103/// * `V` - The value type, with no constraints
104///
105/// # Example
106///
107/// ```rust
108/// # #[cfg(feature = "doctest")]
109/// # {
110/// use sieve_cache::SieveCache;
111///
112/// // Create a new cache with capacity for 1000 items
113/// let mut cache = SieveCache::new(1000).unwrap();
114///
115/// // Insert some values
116/// cache.insert("key1".to_string(), "value1".to_string());
117/// cache.insert("key2".to_string(), "value2".to_string());
118///
119/// // Retrieve values - returns references to the values
120/// assert_eq!(cache.get("key1"), Some(&"value1".to_string()));
121///
122/// // Check if the cache contains a key
123/// assert!(cache.contains_key("key1"));
124/// assert!(!cache.contains_key("missing_key"));
125///
126/// // Get a mutable reference to modify a value
127/// if let Some(value) = cache.get_mut("key1") {
128///     *value = "modified".to_string();
129/// }
130///
131/// // Verify the modification
132/// assert_eq!(cache.get("key1"), Some(&"modified".to_string()));
133///
134/// // Remove a value
135/// let removed = cache.remove("key2");
136/// assert_eq!(removed, Some("value2".to_string()));
137/// # }
138/// ```
139pub struct SieveCache<K: Eq + Hash + Clone, V> {
140    /// Map of keys to indices in the nodes vector
141    map: HashMap<K, usize>,
142    /// Vector of all cache nodes
143    nodes: Vec<Node<K, V>>,
144    /// Index to the "hand" pointer used by the SIEVE algorithm for eviction
145    hand: Option<usize>,
146    /// Maximum number of entries the cache can hold
147    capacity: usize,
148}
149
150impl<K: Eq + Hash + Clone, V> SieveCache<K, V> {
151    /// Creates a new cache with the given capacity.
152    ///
153    /// The capacity represents the maximum number of key-value pairs
154    /// that can be stored in the cache. When this limit is reached,
155    /// the cache will use the SIEVE algorithm to evict entries.
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if the capacity is zero.
160    ///
161    /// # Examples
162    ///
163    /// ```rust
164    /// # #[cfg(feature = "doctest")]
165    /// # {
166    /// use sieve_cache::SieveCache;
167    ///
168    /// // Create a cache with space for 100 entries
169    /// let cache: SieveCache<String, u32> = SieveCache::new(100).unwrap();
170    ///
171    /// // Capacity of zero is invalid
172    /// let invalid = SieveCache::<String, u32>::new(0);
173    /// assert!(invalid.is_err());
174    /// # }
175    /// ```
176    pub fn new(capacity: usize) -> Result<Self, &'static str> {
177        if capacity == 0 {
178            return Err("capacity must be greater than 0");
179        }
180        Ok(Self {
181            map: HashMap::with_capacity(capacity),
182            nodes: Vec::with_capacity(capacity),
183            hand: None,
184            capacity,
185        })
186    }
187
188    /// Returns the capacity of the cache.
189    ///
190    /// This is the maximum number of entries that can be stored before
191    /// the cache begins evicting old entries.
192    ///
193    /// # Examples
194    ///
195    /// ```rust
196    /// # #[cfg(feature = "doctest")]
197    /// # {
198    /// use sieve_cache::SieveCache;
199    ///
200    /// let cache = SieveCache::<String, u32>::new(100).unwrap();
201    /// assert_eq!(cache.capacity(), 100);
202    /// # }
203    /// ```
204    #[inline]
205    pub fn capacity(&self) -> usize {
206        self.capacity
207    }
208
209    /// Returns the number of cached values.
210    ///
211    /// This value will never exceed the capacity.
212    ///
213    /// # Examples
214    ///
215    /// ```rust
216    /// # #[cfg(feature = "doctest")]
217    /// # {
218    /// use sieve_cache::SieveCache;
219    ///
220    /// let mut cache = SieveCache::new(100).unwrap();
221    /// assert_eq!(cache.len(), 0);
222    ///
223    /// cache.insert("key".to_string(), "value".to_string());
224    /// assert_eq!(cache.len(), 1);
225    /// # }
226    /// ```
227    #[inline]
228    pub fn len(&self) -> usize {
229        self.nodes.len()
230    }
231
232    /// Returns `true` when no values are currently cached.
233    ///
234    /// # Examples
235    ///
236    /// ```rust
237    /// # #[cfg(feature = "doctest")]
238    /// # {
239    /// use sieve_cache::SieveCache;
240    ///
241    /// let mut cache = SieveCache::<String, u32>::new(100).unwrap();
242    /// assert!(cache.is_empty());
243    ///
244    /// cache.insert("key".to_string(), "value".to_string());
245    /// assert!(!cache.is_empty());
246    ///
247    /// cache.remove("key");
248    /// assert!(cache.is_empty());
249    /// # }
250    /// ```
251    #[inline]
252    pub fn is_empty(&self) -> bool {
253        self.nodes.is_empty()
254    }
255
256    /// Returns `true` if there is a value in the cache mapped to by `key`.
257    ///
258    /// This operation does not count as an access for the SIEVE algorithm's
259    /// visited flag.
260    ///
261    /// # Examples
262    ///
263    /// ```rust
264    /// # #[cfg(feature = "doctest")]
265    /// # {
266    /// use sieve_cache::SieveCache;
267    ///
268    /// let mut cache = SieveCache::new(100).unwrap();
269    /// cache.insert("key".to_string(), "value".to_string());
270    ///
271    /// assert!(cache.contains_key("key"));
272    /// assert!(!cache.contains_key("missing"));
273    /// # }
274    /// ```
275    #[inline]
276    pub fn contains_key<Q>(&self, key: &Q) -> bool
277    where
278        Q: Hash + Eq + ?Sized,
279        K: Borrow<Q>,
280    {
281        self.map.contains_key(key)
282    }
283
284    /// Gets an immutable reference to the value in the cache mapped to by `key`.
285    ///
286    /// If no value exists for `key`, this returns `None`.
287    ///
288    /// This operation marks the entry as "visited" in the SIEVE algorithm,
289    /// which affects eviction decisions.
290    ///
291    /// # Examples
292    ///
293    /// ```rust
294    /// # #[cfg(feature = "doctest")]
295    /// # {
296    /// use sieve_cache::SieveCache;
297    ///
298    /// let mut cache = SieveCache::new(100).unwrap();
299    /// cache.insert("key".to_string(), "value".to_string());
300    ///
301    /// assert_eq!(cache.get("key"), Some(&"value".to_string()));
302    /// assert_eq!(cache.get("missing"), None);
303    /// # }
304    /// ```
305    pub fn get<Q>(&mut self, key: &Q) -> Option<&V>
306    where
307        Q: Hash + Eq + ?Sized,
308        K: Borrow<Q>,
309    {
310        if let Some(&idx) = self.map.get(key) {
311            // Mark as visited for the SIEVE algorithm
312            self.nodes[idx].visited = true;
313            Some(&self.nodes[idx].value)
314        } else {
315            None
316        }
317    }
318
319    /// Gets a mutable reference to the value in the cache mapped to by `key`.
320    ///
321    /// If no value exists for `key`, this returns `None`.
322    ///
323    /// This operation marks the entry as "visited" in the SIEVE algorithm,
324    /// which affects eviction decisions.
325    ///
326    /// # Examples
327    ///
328    /// ```rust
329    /// # #[cfg(feature = "doctest")]
330    /// # {
331    /// use sieve_cache::SieveCache;
332    ///
333    /// let mut cache = SieveCache::new(100).unwrap();
334    /// cache.insert("key".to_string(), "value".to_string());
335    ///
336    /// // Modify the value in-place
337    /// if let Some(value) = cache.get_mut("key") {
338    ///     *value = "new_value".to_string();
339    /// }
340    ///
341    /// assert_eq!(cache.get("key"), Some(&"new_value".to_string()));
342    /// # }
343    /// ```
344    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
345    where
346        Q: Hash + Eq + ?Sized,
347        K: Borrow<Q>,
348    {
349        if let Some(&idx) = self.map.get(key) {
350            // Mark as visited for the SIEVE algorithm
351            self.nodes[idx].visited = true;
352            Some(&mut self.nodes[idx].value)
353        } else {
354            None
355        }
356    }
357
358    /// Maps `key` to `value` in the cache, possibly evicting old entries.
359    ///
360    /// If the key already exists, its value is updated and the entry is marked as visited.
361    /// If the key doesn't exist and the cache is at capacity, the SIEVE algorithm is used
362    /// to evict an entry before the new key-value pair is inserted.
363    ///
364    /// # Return Value
365    ///
366    /// Returns `true` when this is a new entry, and `false` if an existing entry was updated.
367    ///
368    /// # Examples
369    ///
370    /// ```rust
371    /// # #[cfg(feature = "doctest")]
372    /// # {
373    /// use sieve_cache::SieveCache;
374    ///
375    /// let mut cache = SieveCache::new(100).unwrap();
376    ///
377    /// // Insert a new entry
378    /// let is_new = cache.insert("key1".to_string(), "value1".to_string());
379    /// assert!(is_new); // Returns true for new entries
380    ///
381    /// // Update an existing entry
382    /// let is_new = cache.insert("key1".to_string(), "updated".to_string());
383    /// assert!(!is_new); // Returns false for updates
384    ///
385    /// assert_eq!(cache.get("key1"), Some(&"updated".to_string()));
386    /// # }
387    /// ```
388    pub fn insert(&mut self, key: K, value: V) -> bool {
389        // Check if key already exists
390        if let Some(&idx) = self.map.get(&key) {
391            // Update existing entry
392            self.nodes[idx].visited = true;
393            self.nodes[idx].value = value;
394            return false;
395        }
396
397        // Evict if at capacity
398        if self.nodes.len() >= self.capacity {
399            self.evict();
400        }
401
402        // Add new node to the front
403        let node = Node::new(key.clone(), value);
404        self.nodes.push(node);
405        let idx = self.nodes.len() - 1;
406        self.map.insert(key, idx);
407        true
408    }
409
410    /// Removes the cache entry mapped to by `key`.
411    ///
412    /// This method explicitly removes an entry from the cache regardless of its
413    /// "visited" status.
414    ///
415    /// # Return Value
416    ///
417    /// Returns the value removed from the cache. If `key` did not map to any value,
418    /// then this returns `None`.
419    ///
420    /// # Examples
421    ///
422    /// ```rust
423    /// # #[cfg(feature = "doctest")]
424    /// # {
425    /// use sieve_cache::SieveCache;
426    ///
427    /// let mut cache = SieveCache::new(100).unwrap();
428    /// cache.insert("key1".to_string(), "value1".to_string());
429    /// cache.insert("key2".to_string(), "value2".to_string());
430    ///
431    /// // Remove an existing entry
432    /// let removed = cache.remove("key1");
433    /// assert_eq!(removed, Some("value1".to_string()));
434    ///
435    /// // Try to remove a non-existent entry
436    /// let removed = cache.remove("key1");
437    /// assert_eq!(removed, None);
438    ///
439    /// assert_eq!(cache.len(), 1); // Only one entry remains
440    /// # }
441    /// ```
442    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
443    where
444        K: Borrow<Q>,
445        Q: Eq + Hash + ?Sized,
446    {
447        // Find the node index
448        let idx = self.map.remove(key)?;
449
450        // If this is the last element, just remove it
451        if idx == self.nodes.len() - 1 {
452            return Some(self.nodes.pop().unwrap().value);
453        }
454
455        // Update hand if needed
456        if let Some(hand_idx) = self.hand {
457            if hand_idx == idx {
458                // Move hand to the previous node or wrap to end
459                self.hand = if idx > 0 {
460                    Some(idx - 1)
461                } else {
462                    Some(self.nodes.len() - 2)
463                };
464            } else if hand_idx == self.nodes.len() - 1 {
465                // If hand points to the last element (which will be moved to idx)
466                self.hand = Some(idx);
467            }
468        }
469
470        // Remove the node by replacing it with the last one and updating the map
471        let last_node = self.nodes.pop().unwrap();
472        let removed_value = if idx < self.nodes.len() {
473            // Only need to swap and update map if not the last element
474            let last_key = last_node.key.clone(); // Clone the key before moving
475            let removed_node = std::mem::replace(&mut self.nodes[idx], last_node);
476            self.map.insert(last_key, idx); // Update map for swapped node
477            removed_node.value
478        } else {
479            last_node.value
480        };
481
482        Some(removed_value)
483    }
484
485    /// Removes and returns a value from the cache that was not recently accessed.
486    ///
487    /// This method implements the SIEVE eviction algorithm:
488    /// 1. Starting from the "hand" pointer or the end of the vector, look for an entry
489    ///    that hasn't been visited since the last scan
490    /// 2. Mark all visited entries as non-visited during the scan
491    /// 3. If a non-visited entry is found, evict it
492    /// 4. Update the hand to point to the previous node
493    ///
494    /// # Return Value
495    ///
496    /// If a suitable entry is found, it is removed from the cache and its value is returned.
497    /// If all entries have been recently accessed or the cache is empty, this returns `None`.
498    ///
499    /// # Note
500    ///
501    /// This method is automatically called by `insert` when the cache is at capacity,
502    /// but it can also be called manually to proactively free space.
503    ///
504    /// # Examples
505    ///
506    /// ```rust
507    /// # #[cfg(feature = "doctest")]
508    /// # {
509    /// use sieve_cache::SieveCache;
510    ///
511    /// let mut cache = SieveCache::new(3).unwrap();
512    /// cache.insert("key1".to_string(), "value1".to_string());
513    /// cache.insert("key2".to_string(), "value2".to_string());
514    /// cache.insert("key3".to_string(), "value3".to_string());
515    ///
516    /// // Access key1 and key2 to mark them as visited
517    /// cache.get("key1");
518    /// cache.get("key2");
519    ///
520    /// // key3 hasn't been accessed, so it should be evicted
521    /// let evicted = cache.evict();
522    /// assert!(evicted.is_some());
523    /// assert!(!cache.contains_key("key3")); // key3 was evicted
524    /// # }
525    /// ```
526    pub fn evict(&mut self) -> Option<V> {
527        if self.nodes.is_empty() {
528            return None;
529        }
530
531        // Start from the hand pointer or the end if no hand
532        let mut current_idx = self.hand.unwrap_or(self.nodes.len() - 1);
533        let start_idx = current_idx;
534
535        // Track whether we've wrapped around and whether we found a node to evict
536        let mut wrapped = false;
537        let mut found_idx = None;
538
539        // Scan for a non-visited entry
540        loop {
541            // If current node is not visited, mark it for eviction
542            if !self.nodes[current_idx].visited {
543                found_idx = Some(current_idx);
544                break;
545            }
546
547            // Mark as non-visited for next scan
548            self.nodes[current_idx].visited = false;
549
550            // Move to previous node or wrap to end
551            current_idx = if current_idx > 0 {
552                current_idx - 1
553            } else {
554                // Wrap around to end of vector
555                if wrapped {
556                    // If we've already wrapped, break to avoid infinite loop
557                    break;
558                }
559                wrapped = true;
560                self.nodes.len() - 1
561            };
562
563            // If we've looped back to start, we've checked all nodes
564            if current_idx == start_idx {
565                break;
566            }
567        }
568
569        // If we found a node to evict
570        if let Some(idx) = found_idx {
571            // Update the hand pointer to the previous node or wrap to end
572            self.hand = if idx > 0 {
573                Some(idx - 1)
574            } else if self.nodes.len() > 1 {
575                Some(self.nodes.len() - 2)
576            } else {
577                None
578            };
579
580            // Remove the key from the map
581            let key = &self.nodes[idx].key;
582            self.map.remove(key);
583
584            // Remove the node and return its value
585            if idx == self.nodes.len() - 1 {
586                // If last node, just pop it
587                return Some(self.nodes.pop().unwrap().value);
588            } else {
589                // Otherwise swap with the last node
590                let last_node = self.nodes.pop().unwrap();
591                let last_key = last_node.key.clone(); // Clone the key before moving
592                let removed_node = std::mem::replace(&mut self.nodes[idx], last_node);
593                // Update the map for the moved node
594                self.map.insert(last_key, idx);
595                return Some(removed_node.value);
596            }
597        }
598
599        None
600    }
601
602    /// Removes all entries from the cache.
603    ///
604    /// This operation clears all stored values and resets the cache to an empty state,
605    /// while maintaining the original capacity.
606    ///
607    /// # Examples
608    ///
609    /// ```rust
610    /// # #[cfg(feature = "doctest")]
611    /// # {
612    /// use sieve_cache::SieveCache;
613    ///
614    /// let mut cache = SieveCache::new(100).unwrap();
615    /// cache.insert("key1".to_string(), "value1".to_string());
616    /// cache.insert("key2".to_string(), "value2".to_string());
617    ///
618    /// assert_eq!(cache.len(), 2);
619    ///
620    /// cache.clear();
621    /// assert_eq!(cache.len(), 0);
622    /// assert!(cache.is_empty());
623    /// # }
624    /// ```
625    pub fn clear(&mut self) {
626        self.map.clear();
627        self.nodes.clear();
628        self.hand = None;
629    }
630
631    /// Returns an iterator over all keys in the cache.
632    ///
633    /// The order of keys is not specified and should not be relied upon.
634    ///
635    /// # Examples
636    ///
637    /// ```rust
638    /// # #[cfg(feature = "doctest")]
639    /// # {
640    /// use sieve_cache::SieveCache;
641    /// use std::collections::HashSet;
642    ///
643    /// let mut cache = SieveCache::new(100).unwrap();
644    /// cache.insert("key1".to_string(), "value1".to_string());
645    /// cache.insert("key2".to_string(), "value2".to_string());
646    ///
647    /// let keys: HashSet<_> = cache.keys().collect();
648    /// assert_eq!(keys.len(), 2);
649    /// assert!(keys.contains(&"key1".to_string()));
650    /// assert!(keys.contains(&"key2".to_string()));
651    /// # }
652    /// ```
653    pub fn keys(&self) -> impl Iterator<Item = &K> {
654        self.nodes.iter().map(|node| &node.key)
655    }
656
657    /// Returns an iterator over all values in the cache.
658    ///
659    /// The order of values is not specified and should not be relied upon.
660    ///
661    /// # Examples
662    ///
663    /// ```rust
664    /// # #[cfg(feature = "doctest")]
665    /// # {
666    /// use sieve_cache::SieveCache;
667    /// use std::collections::HashSet;
668    ///
669    /// let mut cache = SieveCache::new(100).unwrap();
670    /// cache.insert("key1".to_string(), "value1".to_string());
671    /// cache.insert("key2".to_string(), "value2".to_string());
672    ///
673    /// let values: HashSet<_> = cache.values().collect();
674    /// assert_eq!(values.len(), 2);
675    /// assert!(values.contains(&"value1".to_string()));
676    /// assert!(values.contains(&"value2".to_string()));
677    /// # }
678    /// ```
679    pub fn values(&self) -> impl Iterator<Item = &V> {
680        self.nodes.iter().map(|node| &node.value)
681    }
682
683    /// Returns an iterator over all mutable values in the cache.
684    ///
685    /// The order of values is not specified and should not be relied upon.
686    /// Note that iterating through this will mark all entries as visited.
687    ///
688    /// # Examples
689    ///
690    /// ```rust
691    /// # #[cfg(feature = "doctest")]
692    /// # {
693    /// use sieve_cache::SieveCache;
694    ///
695    /// let mut cache = SieveCache::new(100).unwrap();
696    /// cache.insert("key1".to_string(), "value1".to_string());
697    /// cache.insert("key2".to_string(), "value2".to_string());
698    ///
699    /// // Update all values by appending text
700    /// for value in cache.values_mut() {
701    ///     *value = format!("{}_updated", value);
702    /// }
703    ///
704    /// assert_eq!(cache.get("key1"), Some(&"value1_updated".to_string()));
705    /// assert_eq!(cache.get("key2"), Some(&"value2_updated".to_string()));
706    /// # }
707    /// ```
708    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
709        for node in &mut self.nodes {
710            node.visited = true;
711        }
712        self.nodes.iter_mut().map(|node| &mut node.value)
713    }
714
715    /// Returns an iterator over all key-value pairs in the cache.
716    ///
717    /// The order of pairs is not specified and should not be relied upon.
718    ///
719    /// # Examples
720    ///
721    /// ```rust
722    /// # #[cfg(feature = "doctest")]
723    /// # {
724    /// use sieve_cache::SieveCache;
725    /// use std::collections::HashMap;
726    ///
727    /// let mut cache = SieveCache::new(100).unwrap();
728    /// cache.insert("key1".to_string(), "value1".to_string());
729    /// cache.insert("key2".to_string(), "value2".to_string());
730    ///
731    /// let entries: HashMap<_, _> = cache.iter().collect();
732    /// assert_eq!(entries.len(), 2);
733    /// assert_eq!(entries.get(&"key1".to_string()), Some(&&"value1".to_string()));
734    /// assert_eq!(entries.get(&"key2".to_string()), Some(&&"value2".to_string()));
735    /// # }
736    /// ```
737    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
738        self.nodes.iter().map(|node| (&node.key, &node.value))
739    }
740
741    /// Returns an iterator over all key-value pairs in the cache, with mutable references to values.
742    ///
743    /// The order of pairs is not specified and should not be relied upon.
744    /// Note that iterating through this will mark all entries as visited.
745    ///
746    /// # Examples
747    ///
748    /// ```rust
749    /// # #[cfg(feature = "doctest")]
750    /// # {
751    /// use sieve_cache::SieveCache;
752    ///
753    /// let mut cache = SieveCache::new(100).unwrap();
754    /// cache.insert("key1".to_string(), "value1".to_string());
755    /// cache.insert("key2".to_string(), "value2".to_string());
756    ///
757    /// // Update all values associated with keys containing '1'
758    /// for (key, value) in cache.iter_mut() {
759    ///     if key.contains('1') {
760    ///         *value = format!("{}_special", value);
761    ///     }
762    /// }
763    ///
764    /// assert_eq!(cache.get("key1"), Some(&"value1_special".to_string()));
765    /// assert_eq!(cache.get("key2"), Some(&"value2".to_string()));
766    /// # }
767    /// ```
768    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
769        for node in &mut self.nodes {
770            node.visited = true;
771        }
772        self.nodes
773            .iter_mut()
774            .map(|node| (&node.key, &mut node.value))
775    }
776
777    /// Retains only the elements specified by the predicate.
778    ///
779    /// Removes all entries for which the provided function returns `false`.
780    /// The elements are visited in arbitrary, unspecified order.
781    ///
782    /// # Examples
783    ///
784    /// ```rust
785    /// # #[cfg(feature = "doctest")]
786    /// # {
787    /// use sieve_cache::SieveCache;
788    ///
789    /// let mut cache = SieveCache::new(100).unwrap();
790    /// cache.insert("key1".to_string(), 100);
791    /// cache.insert("key2".to_string(), 200);
792    /// cache.insert("key3".to_string(), 300);
793    ///
794    /// // Keep only entries with values greater than 150
795    /// cache.retain(|_, value| *value > 150);
796    ///
797    /// assert_eq!(cache.len(), 2);
798    /// assert!(!cache.contains_key("key1"));
799    /// assert!(cache.contains_key("key2"));
800    /// assert!(cache.contains_key("key3"));
801    /// # }
802    /// ```
803    pub fn retain<F>(&mut self, mut f: F)
804    where
805        F: FnMut(&K, &V) -> bool,
806    {
807        // Collect indices to remove
808        let mut to_remove = Vec::new();
809
810        for (i, node) in self.nodes.iter().enumerate() {
811            if !f(&node.key, &node.value) {
812                to_remove.push(i);
813            }
814        }
815
816        // Remove indices from highest to lowest to avoid invalidating other indices
817        to_remove.sort_unstable_by(|a, b| b.cmp(a));
818
819        for idx in to_remove {
820            // Remove from map
821            self.map.remove(&self.nodes[idx].key);
822
823            // If it's the last element, just pop it
824            if idx == self.nodes.len() - 1 {
825                self.nodes.pop();
826            } else {
827                // Replace with the last element
828                let last_idx = self.nodes.len() - 1;
829                // Use swap_remove which replaces the removed element with the last element
830                self.nodes.swap_remove(idx);
831                if idx < self.nodes.len() {
832                    // Update map for the swapped node
833                    self.map.insert(self.nodes[idx].key.clone(), idx);
834                }
835
836                // Update hand if needed
837                if let Some(hand_idx) = self.hand {
838                    if hand_idx == idx {
839                        // Hand was pointing to the removed node, move it to previous
840                        self.hand = if idx > 0 {
841                            Some(idx - 1)
842                        } else if !self.nodes.is_empty() {
843                            Some(self.nodes.len() - 1)
844                        } else {
845                            None
846                        };
847                    } else if hand_idx == last_idx {
848                        // Hand was pointing to the last node that was moved
849                        self.hand = Some(idx);
850                    }
851                }
852            }
853        }
854    }
855}
856
857#[test]
858fn test() {
859    let mut cache = SieveCache::new(3).unwrap();
860    assert!(cache.insert("foo".to_string(), "foocontent".to_string()));
861    assert!(cache.insert("bar".to_string(), "barcontent".to_string()));
862    cache.remove("bar");
863    assert!(cache.insert("bar2".to_string(), "bar2content".to_string()));
864    assert!(cache.insert("bar3".to_string(), "bar3content".to_string()));
865    assert_eq!(cache.get("foo"), Some(&"foocontent".to_string()));
866    assert_eq!(cache.get("bar"), None);
867    assert_eq!(cache.get("bar2"), Some(&"bar2content".to_string()));
868    assert_eq!(cache.get("bar3"), Some(&"bar3content".to_string()));
869}
870
871#[test]
872fn test_visited_flag_update() {
873    let mut cache = SieveCache::new(2).unwrap();
874    cache.insert("key1".to_string(), "value1".to_string());
875    cache.insert("key2".to_string(), "value2".to_string());
876    // update `key1` entry.
877    cache.insert("key1".to_string(), "updated".to_string());
878    // new entry is added.
879    cache.insert("key3".to_string(), "value3".to_string());
880    assert_eq!(cache.get("key1"), Some(&"updated".to_string()));
881}
882
883#[test]
884fn test_clear() {
885    let mut cache = SieveCache::new(10).unwrap();
886    cache.insert("key1".to_string(), "value1".to_string());
887    cache.insert("key2".to_string(), "value2".to_string());
888
889    assert_eq!(cache.len(), 2);
890    assert!(!cache.is_empty());
891
892    cache.clear();
893
894    assert_eq!(cache.len(), 0);
895    assert!(cache.is_empty());
896    assert_eq!(cache.get("key1"), None);
897    assert_eq!(cache.get("key2"), None);
898}
899
900#[test]
901fn test_iterators() {
902    let mut cache = SieveCache::new(10).unwrap();
903    cache.insert("key1".to_string(), "value1".to_string());
904    cache.insert("key2".to_string(), "value2".to_string());
905
906    // Test keys iterator
907    let keys: Vec<&String> = cache.keys().collect();
908    assert_eq!(keys.len(), 2);
909    assert!(keys.contains(&&"key1".to_string()));
910    assert!(keys.contains(&&"key2".to_string()));
911
912    // Test values iterator
913    let values: Vec<&String> = cache.values().collect();
914    assert_eq!(values.len(), 2);
915    assert!(values.contains(&&"value1".to_string()));
916    assert!(values.contains(&&"value2".to_string()));
917
918    // Test values_mut iterator
919    for value in cache.values_mut() {
920        *value = format!("{}_updated", value);
921    }
922
923    assert_eq!(cache.get("key1"), Some(&"value1_updated".to_string()));
924    assert_eq!(cache.get("key2"), Some(&"value2_updated".to_string()));
925
926    // Test key-value iterator
927    let entries: Vec<(&String, &String)> = cache.iter().collect();
928    assert_eq!(entries.len(), 2);
929
930    // Test key-value mutable iterator
931    for (key, value) in cache.iter_mut() {
932        if key == "key1" {
933            *value = format!("{}_special", value);
934        }
935    }
936
937    assert_eq!(
938        cache.get("key1"),
939        Some(&"value1_updated_special".to_string())
940    );
941    assert_eq!(cache.get("key2"), Some(&"value2_updated".to_string()));
942}
943
944#[test]
945fn test_retain() {
946    let mut cache = SieveCache::new(10).unwrap();
947
948    // Add some entries
949    cache.insert("even1".to_string(), 2);
950    cache.insert("even2".to_string(), 4);
951    cache.insert("odd1".to_string(), 1);
952    cache.insert("odd2".to_string(), 3);
953
954    assert_eq!(cache.len(), 4);
955
956    // Keep only entries with even values
957    cache.retain(|_, v| v % 2 == 0);
958
959    assert_eq!(cache.len(), 2);
960    assert!(cache.contains_key("even1"));
961    assert!(cache.contains_key("even2"));
962    assert!(!cache.contains_key("odd1"));
963    assert!(!cache.contains_key("odd2"));
964
965    // Keep only entries with keys containing '1'
966    cache.retain(|k, _| k.contains('1'));
967
968    assert_eq!(cache.len(), 1);
969    assert!(cache.contains_key("even1"));
970    assert!(!cache.contains_key("even2"));
971}