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}