uuid/
non_nil.rs

1//! A wrapper type for nil UUIDs that provides a more memory-efficient
2//! `Option<NonNilUuid>` representation.
3
4use std::{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)]
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 NonNilUuid {
63    /// Creates a non-nil UUID if the value is non-nil.
64    pub const fn new(uuid: Uuid) -> Option<Self> {
65        match NonZeroU128::new(uuid.as_u128()) {
66            Some(non_nil) => Some(NonNilUuid(non_nil)),
67            None => None,
68        }
69    }
70
71    /// Creates a non-nil without checking whether the value is non-nil. This results in undefined behavior if the value is nil.
72    ///
73    /// # Safety
74    ///
75    /// The value must not be nil.
76    pub const unsafe fn new_unchecked(uuid: Uuid) -> Self {
77        NonNilUuid(unsafe { NonZeroU128::new_unchecked(uuid.as_u128()) })
78    }
79
80    /// Get the underlying [`Uuid`] value.
81    #[inline]
82    pub const fn get(self) -> Uuid {
83        Uuid::from_u128(self.0.get())
84    }
85}
86
87impl From<NonNilUuid> for Uuid {
88    /// Converts a [`NonNilUuid`] back into a [`Uuid`].
89    ///
90    /// # Examples
91    /// ```
92    /// # use uuid::{NonNilUuid, Uuid};
93    /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
94    /// let non_nil = NonNilUuid::try_from(uuid).unwrap();
95    /// let uuid_again = Uuid::from(non_nil);
96    ///
97    /// assert_eq!(uuid, uuid_again);
98    /// ```
99    fn from(non_nil: NonNilUuid) -> Self {
100        Uuid::from_u128(non_nil.0.get())
101    }
102}
103
104impl TryFrom<Uuid> for NonNilUuid {
105    type Error = Error;
106
107    /// Attempts to convert a [`Uuid`] into a [`NonNilUuid`].
108    ///
109    /// # Examples
110    /// ```
111    /// # use uuid::{NonNilUuid, Uuid};
112    /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
113    /// let non_nil = NonNilUuid::try_from(uuid).unwrap();
114    /// ```
115    fn try_from(uuid: Uuid) -> Result<Self, Self::Error> {
116        NonZeroU128::new(uuid.as_u128())
117            .map(Self)
118            .ok_or(Error(ErrorKind::Nil))
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_non_nil_with_option_size() {
128        assert_eq!(
129            std::mem::size_of::<Option<NonNilUuid>>(),
130            std::mem::size_of::<Uuid>()
131        );
132    }
133
134    #[test]
135    fn test_non_nil() {
136        let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
137
138        assert_eq!(Uuid::from(NonNilUuid::try_from(uuid).unwrap()), uuid);
139        assert_eq!(NonNilUuid::new(uuid).unwrap(), uuid);
140        assert_eq!(unsafe { NonNilUuid::new_unchecked(uuid) }, uuid);
141
142        assert!(NonNilUuid::try_from(Uuid::nil()).is_err());
143        assert!(NonNilUuid::new(Uuid::nil()).is_none());
144    }
145
146    #[test]
147    fn test_non_nil_formatting() {
148        let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
149        let non_nil = NonNilUuid::try_from(uuid).unwrap();
150
151        assert_eq!(format!("{uuid}"), format!("{non_nil}"));
152        assert_eq!(format!("{uuid:?}"), format!("{non_nil:?}"));
153    }
154}