lfu_cache/lfu/
lfu_entry.rs

1use std::fmt::{Display, Formatter};
2use std::hash::Hash;
3use std::ptr::NonNull;
4use std::rc::Rc;
5
6use super::Node;
7
8#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
9pub(super) struct LfuEntry<Key: Hash + Eq, Value> {
10    // We still need to keep a linked list implementation for O(1)
11    // in-the-middle removal.
12    pub(super) next: Option<NonNull<Self>>,
13    pub(super) prev: Option<NonNull<Self>>,
14    /// Instead of traversing up to the frequency node, we just keep a reference
15    /// to the owning node. This ensures that entry movement is an O(1)
16    /// operation.
17    pub(super) owner: NonNull<Node<Key, Value>>,
18    /// We need to maintain a pointer to the key as we need to remove the
19    /// lookup table entry on lru popping, and we need the key to properly fetch
20    /// the correct entry (the hash itself is not guaranteed to return the
21    /// correct entry).
22    pub(super) key: Rc<Key>,
23    pub(super) value: Value,
24}
25
26#[cfg(not(tarpaulin_include))]
27impl<Key: Hash + Eq, Value: Display> Display for LfuEntry<Key, Value> {
28    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{}", self.value)
30    }
31}
32
33impl<Key: Hash + Eq, Value> LfuEntry<Key, Value> {
34    /// Fully detaches a [`LfuEntry`] entry, removing all references to and from
35    /// it and deallocating its memory address.
36    ///
37    /// This function should only be used when fully removing the item from the
38    /// cache. [`detach`] should be used instead if it will be
39    /// re-attached into the data structure.
40    ///
41    /// [`detach`]: Self::detach
42    pub(super) fn detach_owned(node: NonNull<Self>) -> Detached<Key, Value> {
43        std::mem::forget(Self::detach(node));
44        let detached = unsafe { Box::from_raw(node.as_ptr()) };
45        Detached {
46            key: detached.key,
47            value: detached.value,
48        }
49    }
50
51    /// Removes all references to and from the provided [`LfuEntry`], without
52    /// actually deallocating the memory.
53    ///
54    /// This is useful to avoid deallocating memory and immediately
55    /// reallocating, such as in the common operation of moving a [`LfuEntry`]
56    /// to the next frequency node.
57    pub(super) fn detach(mut node: NonNull<Self>) -> DetachedRef<Key, Value> {
58        // There are five links to fix:
59        // ┌──────┐ (1) ┌─────┐ (2) ┌──────┐
60        // │      ├────►│     ├────►│      │
61        // │ prev │     │ cur │     │ next │
62        // │      │◄────┤     │◄────┤      │
63        // └──────┘ (3) └──┬──┘ (4) └──────┘
64        //                 │
65        //                 │       ┌───────┐
66        //                 │  (5)  │       │
67        //                 └──────►│ owner │
68        //                         │       │
69        //                         └───────┘
70
71        let node_ref = unsafe { node.as_mut() };
72        if let Some(mut prev) = node_ref.prev {
73            // Fix (1)
74            unsafe { prev.as_mut().next = node_ref.next };
75        }
76
77        if let Some(mut next) = node_ref.next {
78            // Fix (4)
79            unsafe { next.as_mut().prev = node_ref.prev };
80        }
81
82        // These are probably not needed but are probably a good idea to do
83        // anyways to have a simpler model to work with.
84
85        // Fix (2)
86        node_ref.next = None;
87        // Fix (3)
88        node_ref.prev = None;
89        // Fix (5)
90        node_ref.owner = NonNull::dangling();
91
92        DetachedRef(node)
93    }
94}
95
96/// Wrapper newtype pattern representing a temporarily detached [`LfuEntry`].
97///
98/// A detached LFU entry is guaranteed to not be internally pointing to
99/// anything. Obtaining a detached LFU entry is also guaranteed to fix any
100/// neighbors that might be pointing to it.
101///
102/// Unlike [`Detached`], this does not deallocate the memory associated with
103/// the [`LfuEntry`]. Instead, this is an optimization for reusing the detached
104/// [`LfuEntry`] at some point after.
105///
106/// # Panics
107///
108/// Because this is intended as an optimization, not re-attaching the detached
109/// value will likely lead to a memory leak. As a result, this intentionally
110/// panics to avoid this scenario.
111#[must_use]
112pub struct DetachedRef<Key: Hash + Eq, Value>(NonNull<LfuEntry<Key, Value>>);
113
114impl<Key: Hash + Eq, Value> Drop for DetachedRef<Key, Value> {
115    fn drop(&mut self) {
116        panic!("Detached reference was dropped. You should re-attach it or use std::mem::forget");
117    }
118}
119
120impl<Key: Hash + Eq, Value> DetachedRef<Key, Value> {
121    pub(super) fn attach_ref(
122        self,
123        prev: Option<NonNull<LfuEntry<Key, Value>>>,
124        next: Option<NonNull<LfuEntry<Key, Value>>>,
125        owner: NonNull<Node<Key, Value>>,
126    ) -> NonNull<LfuEntry<Key, Value>> {
127        // There are five links to fix:
128        // ┌──────┐ (1) ┌─────┐ (2) ┌──────┐
129        // │      ├────►│     ├────►│      │
130        // │ prev │     │ cur │     │ next │
131        // │      │◄────┤     │◄────┤      │
132        // └──────┘ (3) └──┬──┘ (4) └──────┘
133        //                 │
134        //                 │       ┌───────┐
135        //                 │  (5)  │       │
136        //                 └──────►│ owner │
137        //                         │       │
138        //                         └───────┘
139
140        let mut node = self.0;
141        let node_ref = unsafe { node.as_mut() };
142        node_ref.next = next; // Fix (2)
143        node_ref.prev = prev; // Fix (3)
144        node_ref.owner = owner; // Fix (5)
145
146        if let Some(mut prev) = unsafe { node.as_mut() }.prev {
147            // Fixes (1)
148            unsafe { prev.as_mut() }.next = Some(node);
149        }
150
151        if let Some(mut next) = unsafe { node.as_mut() }.next {
152            // Fixes (4)
153            unsafe { next.as_mut() }.prev = Some(node);
154        }
155
156        // DetachedRef explicitly has a drop impl that panics if we don't
157        // explicitly forget about it.
158        std::mem::forget(self);
159        node
160    }
161}
162
163/// Wrapper newtype pattern representing a detached [`LfuEntry`].
164///
165/// A detached LFU entry is guaranteed to not be internally pointing to
166/// anything. Obtaining a detached LFU entry is also guaranteed to fix any
167/// neighbors that might be pointing to it.
168#[must_use]
169#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone)]
170pub(super) struct Detached<Key, Value> {
171    pub(super) key: Rc<Key>,
172    pub(super) value: Value,
173}
174
175impl<Key: Hash + Eq, Value> Detached<Key, Value> {
176    pub fn new(key: Rc<Key>, value: Value) -> Self {
177        Self { key, value }
178    }
179
180    pub fn attach(
181        self,
182        prev: Option<NonNull<LfuEntry<Key, Value>>>,
183        next: Option<NonNull<LfuEntry<Key, Value>>>,
184        owner: NonNull<Node<Key, Value>>,
185    ) -> NonNull<LfuEntry<Key, Value>> {
186        // There are five links to fix:
187        // ┌──────┐ (1) ┌─────┐ (2) ┌──────┐
188        // │      ├────►│     ├────►│      │
189        // │ prev │     │ cur │     │ next │
190        // │      │◄────┤     │◄────┤      │
191        // └──────┘ (3) └──┬──┘ (4) └──────┘
192        //                 │
193        //                 │       ┌───────┐
194        //                 │  (5)  │       │
195        //                 └──────►│ owner │
196        //                         │       │
197        //                         └───────┘
198
199        let new_node = LfuEntry {
200            next,  // Fixes (2)
201            prev,  // Fixes (3)
202            owner, // Fixes (5)
203            key: self.key,
204            value: self.value,
205        };
206
207        let leaked = Box::leak(Box::new(new_node));
208        let mut non_null = NonNull::from(leaked);
209
210        if let Some(mut next) = unsafe { non_null.as_mut() }.next {
211            // Fixes (4)
212            unsafe { next.as_mut() }.prev = Some(non_null);
213        }
214
215        if let Some(mut prev) = unsafe { non_null.as_mut() }.prev {
216            // Fixes (1)
217            unsafe { prev.as_mut() }.next = Some(non_null);
218        }
219        non_null
220    }
221}