intid_allocator/
reusing.rs

1use crate::{IdExhaustedError, UniqueIdAllocator};
2use intid::IntegerIdCounter;
3
4/// A type that allocates integer ids,
5/// with the ability to free unused ids back to storage.
6///
7/// This will minimize the integer value of the keys,
8/// reducing memory needed for lookup tables and bitsets.
9/// It is useful in conjunction with the "direct" maps/sets of the [idmap crate][idmap].
10///
11/// If the ability to free unused ids is not necessary,
12/// consider [`crate::UniqueIdAllocator`] or [`crate::UniqueIdAllocatorAtomic`].
13/// These are more efficient and do not require an allocator.
14///
15/// There is not any way to iterate over all currently allocated ids.
16/// With the current implementation (a [`BinaryHeap`]),
17/// it would be difficult to implement without any allocation.
18///
19/// [idmap]: https://docs.rs/idmap/
20/// [`BinaryHeap`]: alloc::collections::BinaryHeap
21pub struct IdAllocator<T: IntegerIdCounter> {
22    next_id: crate::UniqueIdAllocator<T>,
23    /// Tracks freed ids for reuse, preferring smaller ids where possible
24    ///
25    /// The use of a BinaryHeap here is inspired by the `thread-local` crate.
26    /// No part of the implementation was copied.
27    heap: alloc::collections::BinaryHeap<core::cmp::Reverse<intid::OrderByInt<T>>>,
28}
29impl<T: IntegerIdCounter> Default for IdAllocator<T> {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl<T: IntegerIdCounter> IdAllocator<T> {
36    /// Create a new allocator, with ids starting at [`T::START`] (usually zero).
37    ///
38    /// [`T::START`]: IntegerIdCounter::START
39    #[inline]
40    #[rustversion::attr(since(1.80), const)]
41    pub fn new() -> Self {
42        Self::with_start(T::START)
43    }
44
45    /// Create a new allocator, with ids starting at the specified value.
46    ///
47    /// This function is a `const fn` on all rust versions since 1.80.
48    /// Before that release, it can only be called at runtime.
49    #[inline]
50    #[rustversion::attr(since(1.80), const)]
51    #[rustversion::attr(since(1.80), clippy::msrv = "1.80")]
52    pub fn with_start(start: T) -> Self {
53        IdAllocator {
54            next_id: UniqueIdAllocator::with_start(start),
55            heap: alloc::collections::BinaryHeap::new(),
56        }
57    }
58
59    /// Allocate a new id, reusing freed ids wherever possible.
60    ///
61    /// Returns an error if no more ids are available.
62    #[inline]
63    pub fn try_alloc(&mut self) -> Result<T, IdExhaustedError<T>> {
64        match self.heap.pop() {
65            Some(existing) => Ok(existing.0 .0),
66            None => self.next_id.try_alloc(),
67        }
68    }
69
70    /// Allocate a new id, reusing freed ids wherever possible.
71    ///
72    /// Panics if there are no ids available.
73    #[track_caller]
74    #[inline]
75    #[must_use]
76    pub fn alloc(&mut self) -> T {
77        match self.try_alloc() {
78            Ok(id) => id,
79            Err(e) => e.panic(),
80        }
81    }
82
83    /// Free all existing ids, resetting the allocator.
84    #[inline]
85    pub fn free_all(&mut self) {
86        self.heap.clear();
87        self.next_id.reset();
88    }
89
90    /// Free the specified id, making it available
91    ///
92    /// Used ids will be used in preference to creating new ones.
93    #[inline]
94    pub fn free(&mut self, id: T) {
95        self.heap.push(core::cmp::Reverse(intid::OrderByInt(id)));
96    }
97}