serial_num/lib.rs
1#![doc = include_str!("crate.md")]
2#![doc = include_str!("examples.md")]
3#![cfg_attr(
4 not(any(test, feature = "arbitrary", feature = "bitcode", feature = "speedy",)),
5 no_std
6)]
7
8#[cfg(test)]
9mod tests;
10
11#[cfg(all(test, not(miri), feature = "arbitrary"))]
12mod tests_prop;
13
14#[cfg(kani)]
15mod tests_kani;
16
17#[cfg(all(test, not(miri)))]
18mod tests_readme;
19
20use core::cmp::Ordering;
21use core::ops::Add;
22
23/// Two-byte serial number with wraparound.
24///
25/// A serial number is an identifier assigned incrementally to an item.
26/// In many cases, you can use a `u32` or `u64` and call it
27/// a day, without having to worry about overflow. The niche benefit of this type
28/// is that it only uses the space of a `u16`, with the problem of overflow solved
29/// by wraparound.
30///
31/// This is an "opaque" type, similar to `Instants`.
32/// Serial numbers get their significance when being compare to one another,
33/// but there is no method to get the "inner counter". Another similarity
34/// is that there is no "maximum" serial number, since every
35/// serial number has a successor.
36///
37/// The window used for comparing two serial numbers is half of the number space,
38/// `(u16::MAX-1)/2 = 32767`. If two serial numbers are within that window, we simply compare
39/// the numbers as you normally would. If we compare numbers that do not fit into
40/// that window, like `5` and `65000`, the comparison is flipped, and we say `65000 < 5`.
41/// This is based on the assumption that we got to `5` by increasing `65000` beyond
42/// the point of wraparound at `u16::MAX-1 = 65534`. The assumption only holds if the items you
43/// assign serial numbers to have a short enough lifetime. The ordering of items in your state
44/// will get messed up if there is an item that is the `32767`th successor of another item.
45///
46/// The final value in the number space, `u16::MAX`, is reserved for the special
47/// [`NAN`](Self::NAN) value. This is done to save space - you don't need to wrap
48/// this type in an `Option` if only some items are assigned a serial number.
49#[doc = include_str!("examples.md")]
50#[must_use]
51#[repr(transparent)]
52#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
53#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
54#[cfg_attr(feature = "bincode", derive(bincode::Decode, bincode::Encode))]
55#[cfg_attr(feature = "bitcode", derive(bitcode::Decode, bitcode::Encode))]
56#[cfg_attr(
57 feature = "borsh",
58 derive(borsh::BorshDeserialize, borsh::BorshSerialize)
59)]
60#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
61#[cfg_attr(
62 feature = "postcard",
63 derive(
64 postcard::experimental::max_size::MaxSize,
65 postcard::experimental::schema::Schema
66 )
67)]
68#[cfg_attr(
69 feature = "rkyv",
70 derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize),
71 archive(compare(PartialEq)),
72 archive_attr(derive(Clone, Copy, Debug))
73)]
74#[cfg_attr(feature = "rkyv-safe", archive(check_bytes))]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76#[cfg_attr(feature = "speedy", derive(speedy::Readable, speedy::Writable))]
77pub struct Serial(u16);
78
79const NAN_U16: u16 = u16::MAX;
80const NAN_U32: u32 = 65_535;
81const MAX_U16: u16 = u16::MAX - 1;
82const MID_I32: i32 = 32_767;
83const MID_U16: u16 = 32_767;
84
85impl Serial {
86 /// Special value representing "no serial number".
87 ///
88 /// By convention, this "number" cannot be increased, or added to.
89 pub const NAN: Self = Self(NAN_U16);
90
91 /// Returns `true` if this number is [`NAN`](Self::NAN).
92 #[inline]
93 #[must_use]
94 pub fn is_nan(self) -> bool {
95 self == Self::NAN
96 }
97
98 /// Increases `self` with wraparound.
99 #[inline]
100 pub fn increase(&mut self) {
101 if self.is_nan() {
102 return;
103 }
104 if self.0 < MAX_U16 {
105 self.0 += 1;
106 } else {
107 self.0 = 0; // wraparound
108 }
109 }
110
111 /// Increases `self` with wraparound, and returns a copy.
112 #[inline]
113 pub fn increase_get(&mut self) -> Self {
114 self.increase();
115 *self
116 }
117
118 /// Returns a copy of `self`, and increases `self` with wraparound.
119 #[inline]
120 pub fn get_increase(&mut self) -> Self {
121 let num = *self;
122 self.increase();
123 num
124 }
125
126 /// Distance with wraparound.
127 ///
128 /// For the signed difference, use [`Self::diff()`].
129 ///
130 /// If one of the number is [`NAN`](Self::NAN), the maximum distance of `32767` is returned.
131 /// If both are [`NAN`](Self::NAN), we say the distance is `0`.
132 #[inline]
133 #[must_use]
134 pub fn dist(self, other: Self) -> u16 {
135 if self.is_nan() && other.is_nan() {
136 return 0;
137 }
138 if self.is_nan() || other.is_nan() {
139 return MID_U16; // max distance
140 }
141 if self.0 == other.0 {
142 return 0;
143 }
144
145 let min = self.min(other);
146 let max = self.max(other);
147
148 if min.0 < max.0 {
149 max.0 - min.0
150 } else {
151 // min is less, but has higher number
152 // => sum these distances: min->MAX + 0->max + MAX->0
153 MAX_U16 - min.0 + max.0 + 1
154 }
155 }
156
157 /// Difference with wraparound.
158 ///
159 /// If `self < other`, the result is negative,
160 /// and if `self > other`, the result is positive.
161 ///
162 /// For the unsigned distance, use [`Self::dist()`].
163 ///
164 /// If one of the number is [`NAN`](Self::NAN), the maximum difference of `(-)32767`
165 /// is returned. If both are [`NAN`](Self::NAN), we say the difference is `0`.
166 #[inline]
167 #[must_use]
168 pub fn diff(self, other: Self) -> i16 {
169 let dist = self.dist(other);
170 if self.precedes(other) {
171 -(dist as i16)
172 } else {
173 dist as i16
174 }
175 }
176
177 /// Compares and returns the smaller of two numbers.
178 ///
179 /// The returned number is the "predecessor" of the other.
180 ///
181 /// If one number is [`NAN`](Self::NAN), then the other is returned.
182 #[inline]
183 pub fn min(self, other: Self) -> Self {
184 match self.partial_cmp(other) {
185 Some(Ordering::Less) => self,
186 Some(_) => other,
187 None if self.is_nan() => other,
188 None => self,
189 }
190 }
191
192 /// Compares and returns the larger of two numbers.
193 ///
194 /// The returned number is the "successor" of the other.
195 ///
196 /// If one number is [`NAN`](Self::NAN), then the other is returned.
197 #[inline]
198 pub fn max(self, other: Self) -> Self {
199 match self.partial_cmp(other) {
200 Some(Ordering::Greater) => self,
201 Some(_) => other,
202 None if self.is_nan() => other,
203 None => self,
204 }
205 }
206
207 /// Partial comparison with wraparound.
208 ///
209 /// Returns `None` if one of the values is [`NAN`](Self::NAN).
210 ///
211 /// Based on [RFC1982].
212 ///
213 /// [RFC1982]: https://www.rfc-editor.org/rfc/rfc1982#section-3.2
214 #[inline]
215 #[must_use]
216 pub fn partial_cmp(self, other: Self) -> Option<Ordering> {
217 if self.is_nan() || other.is_nan() {
218 return None;
219 }
220 if self.0 == other.0 {
221 return Some(Ordering::Equal);
222 }
223
224 let a = i32::from(self.0);
225 let b = i32::from(other.0);
226
227 // a < b if either:
228 // - b has the greater number and is within our window
229 // - a has the greater number and is outside our window
230 if (b > a && b - a <= MID_I32) || (a > b && a - b > MID_I32) {
231 Some(Ordering::Less)
232 } else {
233 Some(Ordering::Greater)
234 }
235 }
236
237 /// `True` if `self < other` according to [RFC1982].
238 ///
239 /// [RFC1982]: https://www.rfc-editor.org/rfc/rfc1982#section-3.2
240 #[inline]
241 #[must_use]
242 pub fn precedes(self, other: Self) -> bool {
243 self.partial_cmp(other) == Some(Ordering::Less)
244 }
245
246 /// `True` if `self <= other` according to [RFC1982].
247 ///
248 /// [RFC1982]: https://www.rfc-editor.org/rfc/rfc1982#section-3.2
249 #[inline]
250 #[must_use]
251 pub fn precedes_or_eq(self, other: Self) -> bool {
252 match self.partial_cmp(other) {
253 Some(Ordering::Less | Ordering::Equal) => true,
254 Some(Ordering::Greater) | None => false,
255 }
256 }
257
258 /// `True` if `self > other` according to [RFC1982].
259 ///
260 /// [RFC1982]: https://www.rfc-editor.org/rfc/rfc1982#section-3.2
261 #[inline]
262 #[must_use]
263 pub fn succeeds(self, other: Self) -> bool {
264 self.partial_cmp(other) == Some(Ordering::Greater)
265 }
266
267 /// `True` if `self >= other` according to [RFC1982].
268 ///
269 /// [RFC1982]: https://www.rfc-editor.org/rfc/rfc1982#section-3.2
270 #[inline]
271 #[must_use]
272 pub fn succeeds_or_eq(self, other: Self) -> bool {
273 match self.partial_cmp(other) {
274 Some(Ordering::Greater | Ordering::Equal) => true,
275 Some(Ordering::Less) | None => false,
276 }
277 }
278
279 /// Returns `self` if it's not `NAN`, otherwise returns `other`.
280 #[inline]
281 pub fn or(self, other: Self) -> Self {
282 if self.is_nan() {
283 other
284 } else {
285 self
286 }
287 }
288
289 /// Returns `self` if it's not `NAN`, otherwise returns `Serial::default()`.
290 #[inline]
291 pub fn or_default(self) -> Self {
292 if self.is_nan() {
293 Self::default()
294 } else {
295 self
296 }
297 }
298
299 /// Replaces `self` with `NAN`, returning the previous value.
300 #[inline]
301 pub fn take(&mut self) -> Self {
302 core::mem::replace(self, Self::NAN)
303 }
304}
305
306impl Add<u16> for Serial {
307 type Output = Serial;
308
309 /// Addition with wraparound.
310 ///
311 /// You can add any `u16` to the serial number, but be aware that due to the wraparound
312 /// semantics, adding more than `(u16::MAX-1)/2 = 32767` leads to a result that is
313 /// _less_ than `self`. Adding `u16::MAX` will wraparound to the same value.
314 ///
315 /// If `self.is_nan()`, then the returned serial number is also [`NAN`](Self::NAN).
316 #[inline]
317 fn add(self, rhs: u16) -> Self::Output {
318 if self.is_nan() {
319 return self;
320 }
321 let n = (u32::from(self.0) + u32::from(rhs)) % (NAN_U32);
322 Self(n as u16)
323 }
324}