From 402a8be60c72779194961a3f0ca193e5b48fa393 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 19:15:53 +1000 Subject: [PATCH 1/7] fill in a few non-nil methods --- src/error.rs | 3 +++ src/lib.rs | 4 ++-- src/non_nil.rs | 52 ++++++++++++++++++++++++++++++++++++++---------- src/timestamp.rs | 12 ++++++++--- src/v7.rs | 2 +- 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/error.rs b/src/error.rs index 30e01750..7a65c2d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,6 +30,8 @@ pub(crate) enum ErrorKind { }, /// The input was not a valid UTF8 string InvalidUTF8, + /// The UUID is nil. + Nil, /// Some other error occurred. Other, } @@ -158,6 +160,7 @@ impl fmt::Display for Error { ) } ErrorKind::InvalidUTF8 => write!(f, "non-UTF8 input"), + ErrorKind::Nil => write!(f, "the UUID is nil"), ErrorKind::Other => write!(f, "failed to parse a UUID"), } } diff --git a/src/lib.rs b/src/lib.rs index 385cd6c7..1c30c383 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,10 +227,10 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; mod builder; mod error; +mod non_nil; mod parser; pub mod fmt; -pub mod non_nil; pub mod timestamp; pub use timestamp::{context::NoContext, ClockSequence, Timestamp}; @@ -282,7 +282,7 @@ pub mod __macro_support { use crate::std::convert; -pub use crate::{builder::Builder, error::Error}; +pub use crate::{builder::Builder, error::Error, non_nil::NonNilUuid}; /// A 128-bit (16 byte) buffer containing the UUID. /// diff --git a/src/non_nil.rs b/src/non_nil.rs index 6642a493..71db073e 100644 --- a/src/non_nil.rs +++ b/src/non_nil.rs @@ -4,12 +4,28 @@ use core::convert::TryFrom; use std::{fmt, num::NonZeroU128}; -use crate::Uuid; +use crate::{ + error::{Error, ErrorKind}, + Uuid, +}; /// A UUID that is guaranteed not to be the nil UUID. /// /// This is useful for representing optional UUIDs more efficiently, as `Option` /// takes up the same space as `Uuid`. +/// +/// Note that `Uuid`s created by the following methods are guaranteed to be non-nil: +/// +/// - [`Uuid::new_v1`] +/// - [`Uuid::now_v1`] +/// - [`Uuid::new_v3`] +/// - [`Uuid::new_v4`] +/// - [`Uuid::new_v5`] +/// - [`Uuid::new_v6`] +/// - [`Uuid::now_v6`] +/// - [`Uuid::new_v7`] +/// - [`Uuid::now_v7`] +/// - [`Uuid::new_v8`] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct NonNilUuid(NonZeroU128); @@ -20,7 +36,24 @@ impl fmt::Display for NonNilUuid { } impl NonNilUuid { - /// Returns the underlying `Uuid`. + /// Creates a non-nil UUID if the value is non-nil. + pub const fn new(uuid: Uuid) -> Option { + match NonZeroU128::new(uuid.as_u128()) { + Some(non_nil) => Some(NonNilUuid(non_nil)), + None => None, + } + } + + /// Creates a non-nil without checking whether the value is non-nil. This results in undefined behavior if the value is nil. + /// + /// # Safety + /// + /// The value must not be nil. + pub const unsafe fn new_unchecked(uuid: Uuid) -> Self { + NonNilUuid(unsafe { NonZeroU128::new_unchecked(uuid.as_u128()) }) + } + + /// Get the underlying [`Uuid`] value. #[inline] pub const fn get(self) -> Uuid { Uuid::from_u128(self.0.get()) @@ -47,7 +80,7 @@ impl From for Uuid { } impl TryFrom for NonNilUuid { - type Error = &'static str; + type Error = Error; /// Attempts to convert a [`Uuid`] into a [`NonNilUuid`]. /// @@ -62,7 +95,7 @@ impl TryFrom for NonNilUuid { fn try_from(uuid: Uuid) -> Result { NonZeroU128::new(uuid.as_u128()) .map(Self) - .ok_or("Attempted to convert nil Uuid to NonNilUuid") + .ok_or(Error(ErrorKind::Nil)) } } @@ -81,13 +114,12 @@ mod tests { #[test] fn test_non_nil() { let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef); - let nn_uuid = NonNilUuid::try_from(uuid); - assert!(nn_uuid.is_ok()); - assert_eq!(Uuid::from(nn_uuid.unwrap()), uuid); + assert_eq!(Uuid::from(NonNilUuid::try_from(uuid).unwrap()), uuid); + assert_eq!(NonNilUuid::new(uuid).unwrap().get(), uuid); + assert_eq!(unsafe { NonNilUuid::new_unchecked(uuid) }.get(), uuid); - let nil_uuid = Uuid::nil(); - let nn_uuid = NonNilUuid::try_from(nil_uuid); - assert!(nn_uuid.is_err()); + assert!(NonNilUuid::try_from(Uuid::nil()).is_err()); + assert!(NonNilUuid::new(Uuid::nil()).is_none()); } } diff --git a/src/timestamp.rs b/src/timestamp.rs index 56262f1c..3bf9e9b0 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -128,7 +128,7 @@ impl Timestamp { /// /// If conversion from the internal timestamp format to ticks would overflow /// then it will wrap. - /// + /// /// If the internal counter is wider than 14 bits then it will be truncated to 14 bits. pub const fn to_gregorian(&self) -> (u64, u16) { ( @@ -165,7 +165,10 @@ impl Timestamp { #[doc(hidden)] impl Timestamp { - #[deprecated(since = "1.11.1", note = "use `Timestamp::from_gregorian(ticks, counter)`")] + #[deprecated( + since = "1.11.1", + note = "use `Timestamp::from_gregorian(ticks, counter)`" + )] pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { Timestamp::from_gregorian(ticks, counter) } @@ -175,7 +178,10 @@ impl Timestamp { self.to_gregorian() } - #[deprecated(since = "1.2.0", note = "`Timestamp::to_unix_nanos()` is deprecated and will be removed: use `Timestamp::to_unix()`")] + #[deprecated( + since = "1.2.0", + note = "`Timestamp::to_unix_nanos()` is deprecated and will be removed: use `Timestamp::to_unix()`" + )] pub const fn to_unix_nanos(&self) -> u32 { panic!("`Timestamp::to_unix_nanos()` is deprecated and will be removed: use `Timestamp::to_unix()`") } diff --git a/src/v7.rs b/src/v7.rs index c2b4a223..d55cf946 100644 --- a/src/v7.rs +++ b/src/v7.rs @@ -31,7 +31,7 @@ impl Uuid { /// # Examples /// /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit - /// random number. When supplied as such, the data will be combined + /// random number. When supplied as such, the data will be combined /// to ensure uniqueness and sortability at millisecond granularity. /// /// ```rust From 4ffd872792bd343df4be60fb4fc780b8fa470812 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 19:21:50 +1000 Subject: [PATCH 2/7] add a few missing derives --- src/external/arbitrary_support.rs | 2 +- src/external/serde_support.rs | 2 +- src/lib.rs | 2 +- src/non_nil.rs | 9 +++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/external/arbitrary_support.rs b/src/external/arbitrary_support.rs index 53047dce..c753f74c 100644 --- a/src/external/arbitrary_support.rs +++ b/src/external/arbitrary_support.rs @@ -23,7 +23,7 @@ impl Arbitrary<'_> for Uuid { impl arbitrary::Arbitrary<'_> for NonNilUuid { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let uuid = Uuid::arbitrary(u)?; - Self::try_from(uuid).map_err(|_| arbitrary::Error::NotEnoughData) + Self::try_from(uuid).map_err(|_| arbitrary::Error::IncorrectFormat) } fn size_hint(_: usize) -> (usize, Option) { diff --git a/src/external/serde_support.rs b/src/external/serde_support.rs index 42c06493..954a3496 100644 --- a/src/external/serde_support.rs +++ b/src/external/serde_support.rs @@ -145,7 +145,7 @@ impl<'de> Deserialize<'de> for NonNilUuid { { let uuid = Uuid::deserialize(deserializer)?; - NonNilUuid::try_from(uuid).map_err(|_| de::Error::custom("Uuid cannot be nil")) + NonNilUuid::try_from(uuid).map_err(|_| de::Error::invalid_value(de::Unexpected::Other("nil UUID"), &"a non-nil UUID")) } } diff --git a/src/lib.rs b/src/lib.rs index 1c30c383..d9d772cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -437,6 +437,7 @@ pub enum Variant { /// /// The `Uuid` type is always guaranteed to be have the same ABI as [`Bytes`]. #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] #[cfg_attr( all(uuid_unstable, feature = "zerocopy"), derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned) @@ -445,7 +446,6 @@ pub enum Variant { feature = "borsh", derive(borsh_derive::BorshDeserialize, borsh_derive::BorshSerialize) )] -#[repr(transparent)] #[cfg_attr( feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod, bytemuck::TransparentWrapper) diff --git a/src/non_nil.rs b/src/non_nil.rs index 71db073e..f88fd1a2 100644 --- a/src/non_nil.rs +++ b/src/non_nil.rs @@ -26,6 +26,15 @@ use crate::{ /// - [`Uuid::new_v7`] /// - [`Uuid::now_v7`] /// - [`Uuid::new_v8`] +#[cfg_attr( + all(uuid_unstable, feature = "zerocopy"), + derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned) +)] +#[cfg_attr( + feature = "borsh", + derive(borsh_derive::BorshDeserialize, borsh_derive::BorshSerialize) +)] +#[repr(transparent)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct NonNilUuid(NonZeroU128); From f570b5714c6e25546022e2afe25288492cfecf39 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 19:25:32 +1000 Subject: [PATCH 3/7] support equality between NonNilUuid and Uuid --- src/lib.rs | 1 + src/non_nil.rs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d9d772cf..46abceb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -438,6 +438,7 @@ pub enum Variant { /// The `Uuid` type is always guaranteed to be have the same ABI as [`Bytes`]. #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] #[repr(transparent)] +// NOTE: Also check `NonNilUuid` when ading new derives here #[cfg_attr( all(uuid_unstable, feature = "zerocopy"), derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned) diff --git a/src/non_nil.rs b/src/non_nil.rs index f88fd1a2..66bd0ba9 100644 --- a/src/non_nil.rs +++ b/src/non_nil.rs @@ -44,6 +44,18 @@ impl fmt::Display for NonNilUuid { } } +impl PartialEq for NonNilUuid { + fn eq(&self, other: &Uuid) -> bool { + self.get() == *other + } +} + +impl PartialEq for Uuid { + fn eq(&self, other: &NonNilUuid) -> bool { + *self == other.get() + } +} + impl NonNilUuid { /// Creates a non-nil UUID if the value is non-nil. pub const fn new(uuid: Uuid) -> Option { @@ -125,8 +137,8 @@ mod tests { let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef); assert_eq!(Uuid::from(NonNilUuid::try_from(uuid).unwrap()), uuid); - assert_eq!(NonNilUuid::new(uuid).unwrap().get(), uuid); - assert_eq!(unsafe { NonNilUuid::new_unchecked(uuid) }.get(), uuid); + assert_eq!(NonNilUuid::new(uuid).unwrap(), uuid); + assert_eq!(unsafe { NonNilUuid::new_unchecked(uuid) }, uuid); assert!(NonNilUuid::try_from(Uuid::nil()).is_err()); assert!(NonNilUuid::new(Uuid::nil()).is_none()); From 4021daa72f8d5986a651e8532bbe1c3120868144 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 19:56:38 +1000 Subject: [PATCH 4/7] fix up zerocopy derives --- src/lib.rs | 5 +---- src/non_nil.rs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 46abceb5..edd66430 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,9 +222,6 @@ extern crate std; #[macro_use] extern crate core as std; -#[cfg(all(uuid_unstable, feature = "zerocopy"))] -use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; - mod builder; mod error; mod non_nil; @@ -441,7 +438,7 @@ pub enum Variant { // NOTE: Also check `NonNilUuid` when ading new derives here #[cfg_attr( all(uuid_unstable, feature = "zerocopy"), - derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned) + derive(zerocopy::IntoBytes, zerocopy::FromBytes, zerocopy::KnownLayout, zerocopy::Immutable, zerocopy::Unaligned) )] #[cfg_attr( feature = "borsh", diff --git a/src/non_nil.rs b/src/non_nil.rs index 66bd0ba9..93826543 100644 --- a/src/non_nil.rs +++ b/src/non_nil.rs @@ -28,7 +28,7 @@ use crate::{ /// - [`Uuid::new_v8`] #[cfg_attr( all(uuid_unstable, feature = "zerocopy"), - derive(IntoBytes, FromBytes, KnownLayout, Immutable, Unaligned) + derive(zerocopy::IntoBytes, zerocopy::FromBytes, zerocopy::KnownLayout, zerocopy::Immutable, zerocopy::Unaligned) )] #[cfg_attr( feature = "borsh", From 38df005749e00955d15a2aa3051fa1e1a4669e14 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 20:01:24 +1000 Subject: [PATCH 5/7] remove zerocopy from NonNilUuid for now --- src/non_nil.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/non_nil.rs b/src/non_nil.rs index 93826543..5f3fd85b 100644 --- a/src/non_nil.rs +++ b/src/non_nil.rs @@ -26,10 +26,11 @@ use crate::{ /// - [`Uuid::new_v7`] /// - [`Uuid::now_v7`] /// - [`Uuid::new_v8`] -#[cfg_attr( - all(uuid_unstable, feature = "zerocopy"), - derive(zerocopy::IntoBytes, zerocopy::FromBytes, zerocopy::KnownLayout, zerocopy::Immutable, zerocopy::Unaligned) -)] +/// +/// # ABI +/// +/// The `NonNilUuid` type does not yet have a stable ABI. Its representation or alignment +/// may change. #[cfg_attr( feature = "borsh", derive(borsh_derive::BorshDeserialize, borsh_derive::BorshSerialize) From b12c6909d03d5d375eb917bd9b86899efbbbcea6 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 20:04:58 +1000 Subject: [PATCH 6/7] fix up non nil docs --- src/non_nil.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/non_nil.rs b/src/non_nil.rs index 5f3fd85b..2d7fc546 100644 --- a/src/non_nil.rs +++ b/src/non_nil.rs @@ -87,9 +87,8 @@ impl From for Uuid { /// /// # Examples /// ``` - /// use uuid::{non_nil::NonNilUuid, Uuid}; - /// use std::convert::TryFrom; - /// + /// # use std::convert::TryFrom; + /// # use uuid::{NonNilUuid, Uuid}; /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef); /// let non_nil = NonNilUuid::try_from(uuid).unwrap(); /// let uuid_again = Uuid::from(non_nil); @@ -108,9 +107,8 @@ impl TryFrom for NonNilUuid { /// /// # Examples /// ``` - /// use uuid::{non_nil::NonNilUuid, Uuid}; - /// use std::convert::TryFrom; - /// + /// # use std::convert::TryFrom; + /// # use uuid::{NonNilUuid, Uuid}; /// let uuid = Uuid::from_u128(0x0123456789abcdef0123456789abcdef); /// let non_nil = NonNilUuid::try_from(uuid).unwrap(); /// ``` From 6c5099e7a59dbb4ae610a124f4165f2d28c9b689 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Tue, 14 Jan 2025 20:20:05 +1000 Subject: [PATCH 7/7] also remove borsh from NonNilUuid for now --- src/non_nil.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/non_nil.rs b/src/non_nil.rs index 2d7fc546..ee235a1a 100644 --- a/src/non_nil.rs +++ b/src/non_nil.rs @@ -9,7 +9,7 @@ use crate::{ Uuid, }; -/// A UUID that is guaranteed not to be the nil UUID. +/// A UUID that is guaranteed not to be the [nil UUID](https://www.ietf.org/rfc/rfc9562.html#name-nil-uuid). /// /// This is useful for representing optional UUIDs more efficiently, as `Option` /// takes up the same space as `Uuid`. @@ -30,11 +30,8 @@ use crate::{ /// # ABI /// /// The `NonNilUuid` type does not yet have a stable ABI. Its representation or alignment -/// may change. -#[cfg_attr( - feature = "borsh", - derive(borsh_derive::BorshDeserialize, borsh_derive::BorshSerialize) -)] +/// may change. It is currently only guaranteed that `NonNilUuid` and `Option` +/// are the same size as `Uuid`. #[repr(transparent)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct NonNilUuid(NonZeroU128);