uuid/
non_nil.rs

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