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(Debug, Copy, Clone, PartialEq, Eq, Hash)]
37pub struct NonNilUuid(NonZeroU128);
38
39impl fmt::Display for NonNilUuid {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(f, "{}", Uuid::from(*self))
42    }
43}
44
45impl PartialEq<Uuid> for NonNilUuid {
46    fn eq(&self, other: &Uuid) -> bool {
47        self.get() == *other
48    }
49}
50
51impl PartialEq<NonNilUuid> for Uuid {
52    fn eq(&self, other: &NonNilUuid) -> bool {
53        *self == other.get()
54    }
55}
56
57impl NonNilUuid {
58    /// Creates a non-nil UUID if the value is non-nil.
59    pub const fn new(uuid: Uuid) -> Option<Self> {
60        match NonZeroU128::new(uuid.as_u128()) {
61            Some(non_nil) => Some(NonNilUuid(non_nil)),
62            None => None,
63        }
64    }
65
66    /// Creates a non-nil without checking whether the value is non-nil. This results in undefined behavior if the value is nil.
67    ///
68    /// # Safety
69    ///
70    /// The value must not be nil.
71    pub const unsafe fn new_unchecked(uuid: Uuid) -> Self {
72        NonNilUuid(unsafe { NonZeroU128::new_unchecked(uuid.as_u128()) })
73    }
74
75    /// Get the underlying [`Uuid`] value.
76    #[inline]
77    pub const fn get(self) -> Uuid {
78        Uuid::from_u128(self.0.get())
79    }
80}
81
82impl From<NonNilUuid> for Uuid {
83    /// Converts a [`NonNilUuid`] back into a [`Uuid`].
84    ///
85    /// # Examples
86    /// ```
87    /// # use std::convert::TryFrom;
88    /// # use uuid::{NonNilUuid, Uuid};
89    /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
90    /// let non_nil = NonNilUuid::try_from(uuid).unwrap();
91    /// let uuid_again = Uuid::from(non_nil);
92    ///
93    /// assert_eq!(uuid, uuid_again);
94    /// ```
95    fn from(non_nil: NonNilUuid) -> Self {
96        Uuid::from_u128(non_nil.0.get())
97    }
98}
99
100impl TryFrom<Uuid> for NonNilUuid {
101    type Error = Error;
102
103    /// Attempts to convert a [`Uuid`] into a [`NonNilUuid`].
104    ///
105    /// # Examples
106    /// ```
107    /// # use std::convert::TryFrom;
108    /// # use uuid::{NonNilUuid, Uuid};
109    /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
110    /// let non_nil = NonNilUuid::try_from(uuid).unwrap();
111    /// ```
112    fn try_from(uuid: Uuid) -> Result<Self, Self::Error> {
113        NonZeroU128::new(uuid.as_u128())
114            .map(Self)
115            .ok_or(Error(ErrorKind::Nil))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_non_nil_with_option_size() {
125        assert_eq!(
126            std::mem::size_of::<Option<NonNilUuid>>(),
127            std::mem::size_of::<Uuid>()
128        );
129    }
130
131    #[test]
132    fn test_non_nil() {
133        let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
134
135        assert_eq!(Uuid::from(NonNilUuid::try_from(uuid).unwrap()), uuid);
136        assert_eq!(NonNilUuid::new(uuid).unwrap(), uuid);
137        assert_eq!(unsafe { NonNilUuid::new_unchecked(uuid) }, uuid);
138
139        assert!(NonNilUuid::try_from(Uuid::nil()).is_err());
140        assert!(NonNilUuid::new(Uuid::nil()).is_none());
141    }
142}