intid_allocator/
unique.rs

1use crate::IdExhaustedError;
2use core::cell::Cell;
3use intid::{uint, IntegerIdCounter};
4
5#[cfg(feature = "atomic")]
6pub mod atomic;
7
8/// Allocates unique integer ids.
9///
10/// Guarantees that each call to the [`Self::alloc`] function will return a unique id,
11/// unless [`Self::reset`] is called.
12///
13/// Ids start at [`IntegerIdCounter::START`] by default, counting upwards from there.
14#[derive(Clone, Debug)]
15pub struct UniqueIdAllocator<T: IntegerIdCounter> {
16    next_id: Cell<Option<T>>,
17}
18impl<T: IntegerIdCounter> Default for UniqueIdAllocator<T> {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23impl<T: IntegerIdCounter> UniqueIdAllocator<T> {
24    /// Return the maximum currently used id,
25    /// or `None` if no ids have been allocated yet.
26    #[inline]
27    pub fn max_used_id(&self) -> Option<T> {
28        self.next_id
29            .get()
30            .and_then(|id| IntegerIdCounter::checked_sub(id, uint::one()))
31    }
32
33    /// Create a new allocator,
34    /// using [`T::START`] as the first id (usually zero).
35    ///
36    /// [`T::START`]: IntegerIdCounter::START
37    #[inline]
38    pub const fn new() -> Self {
39        Self::with_start(T::START)
40    }
41
42    /// Create a new allocator,
43    /// using the specified value as the first id.
44    ///
45    /// Equivalent to calling [`Self::new`] then [`Self::set_next_id`] in sequence.
46    #[inline]
47    pub const fn with_start(start: T) -> Self {
48        UniqueIdAllocator {
49            next_id: Cell::new(Some(start)),
50        }
51    }
52
53    /// Attempt to allocate a new id,
54    /// panicking if none are available.
55    ///
56    /// # Panics
57    /// This will panic when the range of the underlying [`IntegerIdCounter`] is exhausted.
58    /// See [`Self::try_alloc`] for a version that returns an error instead.
59    #[inline]
60    #[track_caller]
61    pub fn alloc(&self) -> T {
62        match self.try_alloc() {
63            Ok(id) => id,
64            Err(e) => e.panic(),
65        }
66    }
67
68    /// Attempt to allocate a new id,
69    /// returning an error if there are no more available.
70    ///
71    /// # Errors
72    /// If the range of the underlying [`IntegerIdCounter`] is exhausted,
73    /// this will return an error.
74    #[inline]
75    pub fn try_alloc(&self) -> Result<T, IdExhaustedError<T>> {
76        let old_id = self.next_id.get().ok_or_else(IdExhaustedError::new)?;
77        self.next_id
78            .set(IntegerIdCounter::checked_add(old_id, intid::uint::one()));
79        Ok(old_id)
80    }
81
82    /// Set the id that will be returned from the [`Self::alloc`] function.
83    ///
84    /// Like a call to [`Self::reset`], this may cause the counter to unexpectedly jump backwards.
85    /// It may also cause the counter to jump unexpectedly forwards.
86    /// Keep the allocator private if this behavior is undesired.
87    #[inline]
88    pub fn set_next_id(&self, next_id: T) {
89        self.next_id.set(Some(next_id));
90    }
91
92    /// Reset the allocator to a pristine state,
93    /// beginning allocations all over again.
94    ///
95    /// This is equivalent to running `*allocator = UniqueIdAllocator::new()`,
96    /// but does not require a `&mut Self` reference.
97    ///
98    /// This may cause unexpected behavior if ids are expected to be monotonically increasing,
99    /// or if the new ids conflict with ones still in use.
100    /// To avoid this, keep the id allocator private.
101    ///
102    /// See also the [`Self::set_next_id`] function,
103    /// which can cause the counter to jump forwards in addition to jumping backwards.
104    #[inline]
105    pub fn reset(&self) {
106        self.set_next_id(T::START);
107    }
108}