id_pool/lib.rs
1//! Create and recycle integer ids using a ranged pool.
2//!
3//! This crate is fairly minimalistic and so only deals
4//! with single type of numerical ids. These can be either
5//! `usize` (default), `u64`, `u32` or `u16`, chosen with
6//! the use of appropriate crate feature.
7//!
8//! The main exported structure [`IdPool`] can be
9//! initialized with a custom range and then queried for
10//! new ids that are contained within that range. During
11//! the course of the program, ids can be returned to the
12//! pool to be reused for subsequent id request calls.
13//!
14//! [`IdPool`]: struct.IdPool.html
15
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18
19#[cfg(feature = "u16")]
20type Num = u16;
21#[cfg(feature = "u32")]
22type Num = u32;
23#[cfg(feature = "u64")]
24type Num = u64;
25#[cfg(feature = "usize")]
26type Num = usize;
27
28/// Custom range struct
29#[derive(Copy, Debug, Clone)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31pub struct Range {
32 start: Num,
33 end: Num,
34}
35impl Range {
36 /// Calculates the length of the range.
37 pub fn len(&self) -> Num {
38 self.end - self.start
39 }
40
41 /// Calculates whether a given value is contained
42 /// within the range.
43 pub fn contains(&self, value: &Num) -> bool {
44 value >= &self.start && value <= &self.end
45 }
46}
47
48/// Keeps track of free ids within a specified range,
49/// handles requests and returns of ids based on internal
50/// state.
51///
52/// Internally, a collection of free id ranges is stored.
53/// On a request for an id from the pool, the lowest
54/// available number will be returned to the caller.
55/// Ids can also be returned to the pool to be reused by
56/// subsequent id requests.
57///
58/// # Examples
59///
60/// ```
61/// # use id_pool::IdPool;
62/// // initialize a new pool
63/// let mut pool = IdPool::new();
64/// // request some ids
65/// assert_eq!(Some(1), pool.request_id());
66/// assert_eq!(Some(2), pool.request_id());
67/// assert_eq!(Some(3), pool.request_id());
68/// // return the first id
69/// assert_eq!(Ok(()), pool.return_id(1));
70/// // next request returns recycled first id
71/// assert_eq!(Some(1), pool.request_id());
72/// // subsequent request returns the next free value
73/// assert_eq!(Some(4), pool.request_id());
74/// ```
75#[derive(Debug, Clone)]
76#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
77pub struct IdPool {
78 /// List of available id ranges
79 free: Vec<Range>,
80 /// Number of ids currently in use
81 used: usize,
82}
83
84impl IdPool {
85 /// Creates a new `IdPool` with a default range, which
86 /// starts at `1` and ends at `Num::MAX`.
87 pub fn new() -> Self {
88 Self::new_ranged(1..Num::MAX)
89 }
90
91 /// Creates a new `IdPool` with the given range.
92 pub fn new_ranged(range: std::ops::Range<Num>) -> Self {
93 let vec = vec![Range {
94 start: range.start,
95 end: range.end,
96 }];
97 Self { free: vec, used: 0 }
98 }
99
100 /// Gets the current count of used ids.
101 pub fn used_count(&self) -> usize {
102 self.used
103 }
104
105 /// Returns a new id or `None` if there are no free ids
106 /// in the pool.
107 pub fn request_id(&mut self) -> Option<Num> {
108 // short-circuit if there are no free ranges
109 if self.free.len() == 0 {
110 return None;
111 }
112 // always work on the last range on the list
113 let range = self.free.last_mut().unwrap();
114 // get the first number from the range
115 let id = range.start;
116 // increment range starting point
117 range.start += 1;
118 // if we have just emptied the range then pop it from the list
119 if range.len() == 0 {
120 self.free.pop();
121 }
122 self.used += 1;
123 Some(id)
124 }
125
126 /// Returns an id to the pool or `Err(Num)` if the
127 /// id is already in the pool.
128 pub fn return_id(&mut self, id: Num) -> Result<(), Num> {
129 // search stored ranges for the id in question
130 let position = self.free.binary_search_by(|range| {
131 // match if the id value is adjacent to the range
132 if range.start.checked_sub(1) == Some(id) || range.end == id {
133 std::cmp::Ordering::Equal
134 }
135 // match if the id value is contained within the range
136 else if range.contains(&id) {
137 std::cmp::Ordering::Equal
138 }
139 // otherwise indicate the match must be closer to the id value
140 else {
141 id.cmp(&range.start)
142 }
143 });
144 match position {
145 // range containing id in question was not found,
146 // insert a new range that includes the returned id
147 // at a point in the list that is closest to the id value
148 Err(i) => self.free.insert(
149 i,
150 Range {
151 start: id,
152 end: id + 1,
153 },
154 ),
155 // found range adjacent to or containing the id in question
156 Ok(i) => {
157 let range = &mut self.free[i];
158 // id value adjacent to range start point
159 if range.start.checked_sub(1) == Some(id) {
160 range.start = id;
161 }
162 // id value adjacent to range end point
163 else if range.end == id {
164 range.end = range.end + 1;
165 }
166 // id value contained within one of the ranges,
167 // can't return id to the pool
168 else {
169 return Err(id);
170 }
171 // check if there exists a range before the current one
172 if let Some(before_range_idx) = i.checked_sub(1) {
173 // if the current range's end point is the previous range's
174 // start point, then merge the ranges into one
175 if self.free[before_range_idx].start == self.free[i].end {
176 self.free[before_range_idx].start = self.free[i].start;
177 self.free.remove(i);
178 }
179 }
180 }
181 }
182 self.used -= 1;
183 Ok(())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn request() {
193 let mut pool = IdPool::new();
194 assert_eq!(Some(1), pool.request_id());
195 assert_eq!(Some(2), pool.request_id());
196 assert_eq!(Some(3), pool.request_id());
197 }
198
199 #[test]
200 fn request_return() {
201 let mut pool = IdPool::new();
202 assert_eq!(Some(1), pool.request_id());
203 assert_eq!(Some(2), pool.request_id());
204 assert_eq!(Some(3), pool.request_id());
205 assert_eq!(Ok(()), pool.return_id(1));
206 assert_eq!(Some(1), pool.request_id());
207 assert_eq!(Ok(()), pool.return_id(2));
208 assert_eq!(Some(2), pool.request_id());
209 assert_eq!(Some(4), pool.request_id());
210 assert_eq!(Ok(()), pool.return_id(3));
211 assert_eq!(Ok(()), pool.return_id(4));
212 assert_eq!(Err(5), pool.return_id(5));
213 }
214
215 #[test]
216 fn used_count() {
217 let mut pool = IdPool::new_ranged(1..10);
218 assert_eq!(Some(1), pool.request_id());
219 assert_eq!(Some(2), pool.request_id());
220 assert_eq!(Some(3), pool.request_id());
221 assert_eq!(Ok(()), pool.return_id(1));
222 assert_eq!(2, pool.used_count());
223 }
224
225 #[test]
226 fn return_last() {
227 let mut pool = IdPool::new_ranged(1..10);
228 assert_eq!(Some(1), pool.request_id());
229 assert_eq!(Some(2), pool.request_id());
230 assert_eq!(Some(3), pool.request_id());
231 assert_eq!(Ok(()), pool.return_id(3));
232 }
233}