uuid/
non_nil.rs

1//! A wrapper type for nil UUIDs that provides a more memory-efficient
2//! `Option<NonNilUuid>` representation.
3
4use std::{cmp::Ordering, fmt, num::NonZeroU128};
5
6use crate::{
7    error::{Error, ErrorKind},
8    Uuid,
9};
10
11/// A UUID that is guaranteed not to be the [nil UUID](https://www.ietf.org/rfc/rfc9562.html#name-nil-uuid).
12///
13/// This is useful for representing optional UUIDs more efficiently, as `Option<NonNilUuid>`
14/// takes up the same space as `Uuid`.
15///
16/// Note that `Uuid`s created by the following methods are guaranteed to be non-nil:
17///
18/// - [`Uuid::new_v1`]
19/// - [`Uuid::now_v1`]
20/// - [`Uuid::new_v3`]
21/// - [`Uuid::new_v4`]
22/// - [`Uuid::new_v5`]
23/// - [`Uuid::new_v6`]
24/// - [`Uuid::now_v6`]
25/// - [`Uuid::new_v7`]
26/// - [`Uuid::now_v7`]
27/// - [`Uuid::new_v8`]
28///
29/// # ABI
30///
31/// The `NonNilUuid` type does not yet have a stable ABI. Its representation or alignment
32/// may change. It is currently only guaranteed that `NonNilUuid` and `Option<NonNilUuid>`
33/// are the same size as `Uuid`.
34#[repr(transparent)]
35#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
36pub struct NonNilUuid(NonZeroU128);
37
38impl fmt::Debug for NonNilUuid {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        fmt::Debug::fmt(&Uuid::from(*self), f)
41    }
42}
43
44impl fmt::Display for NonNilUuid {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        fmt::Display::fmt(&Uuid::from(*self), f)
47    }
48}
49
50impl PartialEq<Uuid> for NonNilUuid {
51    fn eq(&self, other: &Uuid) -> bool {
52        self.get() == *other
53    }
54}
55
56impl PartialEq<NonNilUuid> for Uuid {
57    fn eq(&self, other: &NonNilUuid) -> bool {
58        *self == other.get()
59    }
60}
61
62impl PartialOrd<Uuid> for NonNilUuid {
63    fn partial_cmp(&self, other: &Uuid) -> Option<Ordering> {
64        self.get().partial_cmp(other)
65    }
66}
67
68impl PartialOrd<NonNilUuid> for Uuid {
69    fn partial_cmp(&self, other: &NonNilUuid) -> Option<Ordering> {
70        self.partial_cmp(&other.get())
71    }
72}
73
74impl NonNilUuid {
75    /// Creates a non-nil UUID if the value is non-nil.
76    pub const fn new(uuid: Uuid) -> Option<Self> {
77        match NonZeroU128::new(uuid.as_u128()) {
78            Some(non_nil) => Some(NonNilUuid(non_nil)),
79            None => None,
80        }
81    }
82
83    /// Creates a non-nil without checking whether the value is non-nil. This results in undefined behavior if the value is nil.
84    ///
85    /// # Safety
86    ///
87    /// The value must not be nil.
88    pub const unsafe fn new_unchecked(uuid: Uuid) -> Self {
89        NonNilUuid(unsafe { NonZeroU128::new_unchecked(uuid.as_u128()) })
90    }
91
92    /// Get the underlying [`Uuid`] value.
93    #[inline]
94    pub const fn get(self) -> Uuid {
95        Uuid::from_u128(self.0.get())
96    }
97}
98
99impl From<NonNilUuid> for Uuid {
100    /// Converts a [`NonNilUuid`] back into a [`Uuid`].
101    ///
102    /// # Examples
103    /// ```
104    /// # use uuid::{NonNilUuid, Uuid};
105    /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
106    /// let non_nil = NonNilUuid::try_from(uuid).unwrap();
107    /// let uuid_again = Uuid::from(non_nil);
108    ///
109    /// assert_eq!(uuid, uuid_again);
110    /// ```
111    fn from(non_nil: NonNilUuid) -> Self {
112        Uuid::from_u128(non_nil.0.get())
113    }
114}
115
116impl TryFrom<Uuid> for NonNilUuid {
117    type Error = Error;
118
119    /// Attempts to convert a [`Uuid`] into a [`NonNilUuid`].
120    ///
121    /// # Examples
122    /// ```
123    /// # use uuid::{NonNilUuid, Uuid};
124    /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
125    /// let non_nil = NonNilUuid::try_from(uuid).unwrap();
126    /// ```
127    fn try_from(uuid: Uuid) -> Result<Self, Self::Error> {
128        NonZeroU128::new(uuid.as_u128())
129            .map(Self)
130            .ok_or(Error(ErrorKind::Nil))
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_non_nil_with_option_size() {
140        assert_eq!(
141            std::mem::size_of::<Option<NonNilUuid>>(),
142            std::mem::size_of::<Uuid>()
143        );
144    }
145
146    #[test]
147    fn test_non_nil() {
148        let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
149
150        assert_eq!(Uuid::from(NonNilUuid::try_from(uuid).unwrap()), uuid);
151        assert_eq!(NonNilUuid::new(uuid).unwrap(), uuid);
152        assert_eq!(unsafe { NonNilUuid::new_unchecked(uuid) }, uuid);
153
154        assert!(NonNilUuid::try_from(Uuid::nil()).is_err());
155        assert!(NonNilUuid::new(Uuid::nil()).is_none());
156    }
157
158    #[test]
159    fn test_non_nil_formatting() {
160        let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
161        let non_nil = NonNilUuid::try_from(uuid).unwrap();
162
163        assert_eq!(format!("{uuid}"), format!("{non_nil}"));
164        assert_eq!(format!("{uuid:?}"), format!("{non_nil:?}"));
165    }
166
167    #[test]
168    fn test_non_nil_ord() {
169        let uuid1 = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
170        let uuid2 = Uuid::from_u128(0x0123456789abcdef0123456789abcdf0);
171
172        let non_nil1 = NonNilUuid::try_from(uuid1).unwrap();
173        let non_nil2 = NonNilUuid::try_from(uuid2).unwrap();
174
175        // Ordering between NonNilUuid instances
176        assert!(non_nil1 < non_nil2);
177        assert!(non_nil2 > non_nil1);
178
179        // Ordering between NonNilUuid and Uuid
180        assert!(non_nil1 < uuid2);
181        assert!(non_nil2 > uuid1);
182
183        // Ordering between Uuid and NonNilUuid
184        assert!(uuid1 < non_nil2);
185        assert!(uuid2 > non_nil1);
186
187        // Equality
188        let non_nil1_copy = NonNilUuid::try_from(uuid1).unwrap();
189        assert_eq!(non_nil1, non_nil1_copy);
190        assert!(non_nil1 <= non_nil1_copy);
191        assert!(non_nil1 >= non_nil1_copy);
192
193        // Equality between NonNilUuid and Uuid
194        assert_eq!(non_nil1, uuid1);
195        assert_eq!(uuid1, non_nil1);
196        assert!(non_nil1 >= uuid1);
197        assert!(non_nil1 <= uuid1);
198        assert!(uuid1 >= non_nil1);
199        assert!(uuid1 <= non_nil1);
200    }
201}