Skip to main content

quick_cache/
linked_slab.rs

1pub type Token = std::num::NonZeroU32;
2
3#[derive(Debug, Clone)]
4struct Entry<T> {
5    item: Option<T>,
6    /// The next item in the list (possibly itself).
7    /// When `item` is None, `next` points ot the next free token in the slab.
8    next: Token,
9    /// The previous item in the list (possibly itself).
10    /// Unused if `item` is None.
11    prev: Token,
12}
13
14/// A slab like structure that also maintains circular lists with its items.
15#[derive(Debug, Clone)]
16pub struct LinkedSlab<T> {
17    entries: Vec<Entry<T>>,
18    next_free: Token,
19}
20
21impl<T> LinkedSlab<T> {
22    pub fn with_capacity(capacity: usize) -> Self {
23        Self {
24            entries: Vec::with_capacity(capacity),
25            next_free: Token::new(1).unwrap(),
26        }
27    }
28
29    /// Reserves capacity for at least `additional` more entries to be inserted.
30    ///
31    /// This pre-allocates the backing storage so that subsequent `insert`s don't
32    /// trigger a reallocation (and the associated copy) of the whole slab.
33    pub fn reserve(&mut self, additional: usize) {
34        self.entries.reserve(additional);
35    }
36
37    /// The number of entries the slab can hold without reallocating.
38    #[cfg(test)]
39    pub fn capacity(&self) -> usize {
40        self.entries.capacity()
41    }
42
43    #[cfg(fuzzing)]
44    pub fn len(&self) -> usize {
45        self.entries.iter().filter(|e| e.item.is_some()).count()
46    }
47
48    #[cfg(any(fuzzing, test))]
49    pub fn iter_entries(&self) -> impl Iterator<Item = &T> + '_ {
50        self.entries.iter().filter_map(|e| e.item.as_ref())
51    }
52
53    #[cfg(any(fuzzing, test))]
54    pub fn validate(&self) {
55        let mut freelist = std::collections::HashSet::new();
56        let mut next_free = self.next_free;
57        while next_free.get() as usize - 1 != self.entries.len() {
58            freelist.insert(next_free.get() as usize);
59            let e = &self.entries[next_free.get() as usize - 1];
60            assert!(e.item.is_none(), "{next_free} is in freelist but has item");
61            next_free = e.next;
62        }
63        for (i, e) in self.entries.iter().enumerate() {
64            if e.item.is_some() {
65                assert!(!freelist.contains(&(i + 1)));
66                assert!(!freelist.contains(&(e.prev.get() as usize)));
67                assert!(!freelist.contains(&(e.next.get() as usize)));
68            }
69        }
70    }
71
72    /// Inserts a new entry in the list.
73    /// Initially, the item will belong to a list only containing itself.
74    ///
75    /// # Panics
76    /// Panics if number of items exceed `u32::MAX - 1`.
77    pub fn insert(&mut self, item: T) -> Token {
78        let token = self.next_free;
79        // eprintln!("linkedslab::insert token {token} head {head:?}");
80        let idx = (token.get() - 1) as usize;
81        if idx < self.entries.len() {
82            let entry = &mut self.entries[idx];
83            self.next_free = entry.next;
84            (entry.prev, entry.next) = (token, token);
85            debug_assert!(entry.item.is_none());
86            entry.item = Some(item);
87        } else {
88            debug_assert_eq!(idx, self.entries.len());
89            self.next_free = Token::new(token.get().wrapping_add(1)).expect("Capacity overflow");
90            self.entries.push(Entry {
91                next: token,
92                prev: token,
93                item: Some(item),
94            });
95        }
96        token
97    }
98
99    /// Gets an entry and a token to the next entry.
100    #[inline]
101    pub fn get(&self, index: Token) -> Option<(&T, Token)> {
102        if let Some(entry) = self.entries.get((index.get() - 1) as usize) {
103            if let Some(v) = &entry.item {
104                return Some((v, entry.next));
105            }
106        }
107        None
108    }
109
110    /// Gets an entry and a token to the next entry.
111    #[inline]
112    pub fn get_mut(&mut self, index: Token) -> Option<(&mut T, Token)> {
113        if let Some(entry) = self.entries.get_mut((index.get() - 1) as usize) {
114            if let Some(v) = &mut entry.item {
115                return Some((v, entry.next));
116            }
117        }
118        None
119    }
120
121    /// Gets an entry and a token to the next entry w/o checking, thus unsafe.
122    #[inline]
123    pub unsafe fn get_unchecked(&self, index: Token) -> (&T, Token) {
124        unsafe {
125            let entry = self.entries.get_unchecked((index.get() - 1) as usize);
126            let v = entry.item.as_ref().unwrap_unchecked();
127            (v, entry.next)
128        }
129    }
130
131    /// Gets an entry and a token to the next entry w/o checking, thus unsafe.
132    #[inline]
133    pub unsafe fn get_mut_unchecked(&mut self, index: Token) -> (&mut T, Token) {
134        unsafe {
135            let entry = self.entries.get_unchecked_mut((index.get() - 1) as usize);
136            let v = entry.item.as_mut().unwrap_unchecked();
137            (v, entry.next)
138        }
139    }
140
141    /// Links an entry before `target_head`. Returns the item next to the linked item,
142    /// which is either the item itself or `target_head`.
143    ///
144    /// # Panics
145    /// Panics on out of bounds access.
146    /// Panics (in debug mode) if linking an absent entry.
147    pub fn link(&mut self, idx: Token, target_head: Option<Token>) -> Token {
148        // eprintln!("linkedslab::link {idx} head {target_head:?}");
149        let (prev, next) = if let Some(target_head) = target_head {
150            let head = &mut self.entries[(target_head.get() - 1) as usize];
151            debug_assert!(head.item.is_some());
152            if head.prev == target_head {
153                // existing list has a single item linking to itself
154                head.prev = idx;
155                head.next = idx;
156                (target_head, target_head)
157            } else {
158                let before_head_idx = head.prev;
159                head.prev = idx;
160                let before_head = &mut self.entries[(before_head_idx.get() - 1) as usize];
161                debug_assert!(before_head.item.is_some());
162                debug_assert_eq!(before_head.next, target_head);
163                before_head.next = idx;
164                (before_head_idx, target_head)
165            }
166        } else {
167            (idx, idx)
168        };
169
170        let entry = &mut self.entries[(idx.get() - 1) as usize];
171        debug_assert!(entry.item.is_some());
172        debug_assert_eq!(entry.next, idx);
173        debug_assert_eq!(entry.prev, idx);
174        (entry.prev, entry.next) = (prev, next);
175        next
176    }
177
178    /// Unlinks an entry without removing it from the ring
179    /// Returns next item in the list, if not self.
180    ///
181    /// # Panics
182    /// Panics on out of bounds access.
183    /// Panics (in debug mode) if unlinking an absent entry.
184    pub fn unlink(&mut self, idx: Token) -> Option<Token> {
185        // eprintln!("linkedslab::unlink {idx}");
186        let entry = &mut self.entries[(idx.get() - 1) as usize];
187        debug_assert!(entry.item.is_some());
188        let (prev_idx, next_idx) = (entry.prev, entry.next);
189        if next_idx == idx {
190            debug_assert_eq!(prev_idx, idx);
191            // single item list, nothing to do
192            None
193        } else {
194            (entry.prev, entry.next) = (idx, idx);
195            let next = &mut self.entries[(next_idx.get() - 1) as usize];
196            debug_assert!(next.item.is_some());
197            debug_assert_eq!(next.prev, idx);
198            next.prev = prev_idx;
199            let prev = &mut self.entries[(prev_idx.get() - 1) as usize];
200            debug_assert!(prev.item.is_some());
201            debug_assert_eq!(prev.next, idx);
202            prev.next = next_idx;
203            Some(next_idx)
204        }
205    }
206
207    /// Unlinks and removes the entry from the ring.
208    /// Returns next item in the list, if not self.
209    ///
210    /// # Panics
211    /// Panics on out of bounds access.
212    pub fn remove(&mut self, idx: Token) -> Option<(T, Option<Token>)> {
213        // eprintln!("linkedslab::remove {idx}");
214        let next = self.unlink(idx);
215        let entry = &mut self.entries[(idx.get() - 1) as usize];
216        let old_item = entry.item.take()?;
217        entry.next = self.next_free;
218        self.next_free = idx;
219        Some((old_item, next))
220    }
221
222    /// The Token that will be returned by the next call to `insert()`
223    pub fn next_free(&self) -> Token {
224        self.next_free
225    }
226
227    /// Drains all items from the slab.
228    ///
229    /// The slab will be emptied even if the returned iterator isn't fully consumed.
230    pub fn drain(&mut self) -> impl Iterator<Item = T> + '_ {
231        self.next_free = Token::new(1).unwrap();
232        self.entries.drain(..).flat_map(|i| i.item)
233    }
234
235    /// Iterator for the items in the slab
236    pub fn iter(&self) -> impl Iterator<Item = &'_ T> + '_ {
237        self.entries.iter().flat_map(|i| i.item.as_ref())
238    }
239
240    /// Iterator for the items in the slab, starting after a given token.
241    pub fn iter_from(
242        &self,
243        continuation: Option<Token>,
244    ) -> impl Iterator<Item = (Token, &'_ T)> + '_ {
245        let skip = continuation.map_or(0, |c| c.get() as usize);
246        self.entries
247            .iter()
248            .skip(skip)
249            .enumerate()
250            .filter_map(move |(i, e)| {
251                if let Some(item) = &e.item {
252                    Some((Token::new((skip + i + 1) as u32)?, item))
253                } else {
254                    None
255                }
256            })
257    }
258
259    /// Get the memory used by LinkedSlab
260    ///
261    /// It should be noted that if cache key or value is some type like `Vec<T>`,
262    /// the memory allocated in the heap will not be counted.
263    pub fn memory_used(&self) -> usize {
264        self.entries.len() * std::mem::size_of::<Entry<T>>()
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[test]
273    fn reserve_avoids_realloc() {
274        let mut slab: LinkedSlab<u64> = LinkedSlab::with_capacity(0);
275        slab.reserve(1000);
276        let cap = slab.entries.capacity();
277        assert!(cap >= 1000);
278        // Inserting up to the reserved capacity must not reallocate.
279        for i in 0..cap as u64 {
280            slab.insert(i);
281        }
282        assert_eq!(
283            slab.entries.capacity(),
284            cap,
285            "slab reallocated despite reserve"
286        );
287    }
288}