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}