From 4dd582806081d6718b7d0cac303c241d9a7eb0c9 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Tue, 8 Jul 2025 15:02:02 -0700 Subject: [PATCH 1/2] Remove some unsafe; stabilize zerocopy Remove a number of `unsafe` blocks, replacing them with uses of zerocopy. In order to do this, we stabilize zerocopy as a (non-optional) dependency. Closes #588 --- Cargo.toml | 7 +-- src/builder.rs | 3 +- src/fmt.rs | 145 ++++++++++++++++++++++++++++++++++--------------- src/lib.rs | 17 +++--- 4 files changed, 111 insertions(+), 61 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0e920a3..c397db0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,13 +104,8 @@ version = "2" optional = true version = "1.1.3" -# Public (unstable): Used in `zerocopy` derive -# Unstable: also need RUSTFLAGS="--cfg uuid_unstable" to work -# This feature may break between releases, or be removed entirely before -# stabilization. -# See: https://github.com/uuid-rs/uuid/issues/588 +# Public: Used in trait impls on `Uuid` [dependencies.zerocopy] -optional = true version = "0.8" features = ["derive"] diff --git a/src/builder.rs b/src/builder.rs index a544a133..8af417f2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -431,8 +431,7 @@ impl Uuid { /// ``` #[inline] pub fn from_bytes_ref(bytes: &Bytes) -> &Uuid { - // SAFETY: `Bytes` and `Uuid` have the same ABI - unsafe { &*(bytes as *const Bytes as *const Uuid) } + zerocopy::transmute_ref!(bytes) } // NOTE: There is no `from_u128_ref` because in little-endian diff --git a/src/fmt.rs b/src/fmt.rs index bbfb1130..86a35ec1 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -11,10 +11,12 @@ //! Adapters for alternative string formats. -use core::str::FromStr; +use core::{convert::TryInto as _, str::FromStr}; + +use zerocopy::{transmute_ref, FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; use crate::{ - std::{borrow::Borrow, fmt, ptr, str}, + std::{borrow::Borrow, fmt, str}, Error, Uuid, Variant, }; @@ -67,25 +69,85 @@ impl fmt::UpperHex for Uuid { /// Format a [`Uuid`] as a hyphenated string, like /// `67e55044-10b1-426f-9247-bb680e5fe0c8`. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + IntoBytes, + FromBytes, + KnownLayout, + Immutable, + Unaligned, +)] #[repr(transparent)] pub struct Hyphenated(Uuid); /// Format a [`Uuid`] as a simple string, like /// `67e5504410b1426f9247bb680e5fe0c8`. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + IntoBytes, + FromBytes, + KnownLayout, + Immutable, + Unaligned, +)] #[repr(transparent)] pub struct Simple(Uuid); /// Format a [`Uuid`] as a URN string, like /// `urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8`. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + IntoBytes, + FromBytes, + KnownLayout, + Immutable, + Unaligned, +)] #[repr(transparent)] pub struct Urn(Uuid); /// Format a [`Uuid`] as a braced hyphenated string, like /// `{67e55044-10b1-426f-9247-bb680e5fe0c8}`. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + IntoBytes, + FromBytes, + KnownLayout, + Immutable, + Unaligned, +)] #[repr(transparent)] pub struct Braced(Uuid); @@ -99,8 +161,7 @@ impl Uuid { /// Get a borrowed [`Hyphenated`] formatter. #[inline] pub fn as_hyphenated(&self) -> &Hyphenated { - // SAFETY: `Uuid` and `Hyphenated` have the same ABI - unsafe { &*(self as *const Uuid as *const Hyphenated) } + transmute_ref!(self) } /// Get a [`Simple`] formatter. @@ -112,8 +173,7 @@ impl Uuid { /// Get a borrowed [`Simple`] formatter. #[inline] pub fn as_simple(&self) -> &Simple { - // SAFETY: `Uuid` and `Simple` have the same ABI - unsafe { &*(self as *const Uuid as *const Simple) } + transmute_ref!(self) } /// Get a [`Urn`] formatter. @@ -125,8 +185,7 @@ impl Uuid { /// Get a borrowed [`Urn`] formatter. #[inline] pub fn as_urn(&self) -> &Urn { - // SAFETY: `Uuid` and `Urn` have the same ABI - unsafe { &*(self as *const Uuid as *const Urn) } + transmute_ref!(self) } /// Get a [`Braced`] formatter. @@ -138,8 +197,7 @@ impl Uuid { /// Get a borrowed [`Braced`] formatter. #[inline] pub fn as_braced(&self) -> &Braced { - // SAFETY: `Uuid` and `Braced` have the same ABI - unsafe { &*(self as *const Uuid as *const Braced) } + transmute_ref!(self) } } @@ -194,43 +252,46 @@ const fn format_hyphenated(src: &[u8; 16], upper: bool) -> [u8; 36] { #[inline] fn encode_simple<'b>(src: &[u8; 16], buffer: &'b mut [u8], upper: bool) -> &'b mut str { let buf = &mut buffer[..Simple::LENGTH]; - let dst = buf.as_mut_ptr(); + let buf: &mut [u8; Simple::LENGTH] = buf.try_into().unwrap(); + *buf = format_simple(src, upper); - // SAFETY: `buf` is guaranteed to be at least `LEN` bytes // SAFETY: The encoded buffer is ASCII encoded - unsafe { - ptr::write(dst.cast(), format_simple(src, upper)); - str::from_utf8_unchecked_mut(buf) - } + unsafe { str::from_utf8_unchecked_mut(buf) } } #[inline] fn encode_hyphenated<'b>(src: &[u8; 16], buffer: &'b mut [u8], upper: bool) -> &'b mut str { let buf = &mut buffer[..Hyphenated::LENGTH]; - let dst = buf.as_mut_ptr(); + let buf: &mut [u8; Hyphenated::LENGTH] = buf.try_into().unwrap(); + *buf = format_hyphenated(src, upper); - // SAFETY: `buf` is guaranteed to be at least `LEN` bytes // SAFETY: The encoded buffer is ASCII encoded - unsafe { - ptr::write(dst.cast(), format_hyphenated(src, upper)); - str::from_utf8_unchecked_mut(buf) - } + unsafe { str::from_utf8_unchecked_mut(buf) } } #[inline] fn encode_braced<'b>(src: &[u8; 16], buffer: &'b mut [u8], upper: bool) -> &'b mut str { - let buf = &mut buffer[..Braced::LENGTH]; - buf[0] = b'{'; - buf[Braced::LENGTH - 1] = b'}'; + let buf = &mut buffer[..Hyphenated::LENGTH + 2]; + let buf: &mut [u8; Hyphenated::LENGTH + 2] = buf.try_into().unwrap(); + + #[derive(IntoBytes)] + #[repr(C)] + struct Braced { + open_curly: u8, + hyphenated: [u8; Hyphenated::LENGTH], + close_curly: u8, + } - // SAFETY: `buf` is guaranteed to be at least `LEN` bytes - // SAFETY: The encoded buffer is ASCII encoded - unsafe { - let dst = buf.as_mut_ptr().add(1); + let braced = Braced { + open_curly: b'{', + hyphenated: format_hyphenated(src, upper), + close_curly: b'}', + }; - ptr::write(dst.cast(), format_hyphenated(src, upper)); - str::from_utf8_unchecked_mut(buf) - } + *buf = zerocopy::transmute!(braced); + + // SAFETY: The encoded buffer is ASCII encoded + unsafe { str::from_utf8_unchecked_mut(buf) } } #[inline] @@ -238,14 +299,12 @@ fn encode_urn<'b>(src: &[u8; 16], buffer: &'b mut [u8], upper: bool) -> &'b mut let buf = &mut buffer[..Urn::LENGTH]; buf[..9].copy_from_slice(b"urn:uuid:"); - // SAFETY: `buf` is guaranteed to be at least `LEN` bytes - // SAFETY: The encoded buffer is ASCII encoded - unsafe { - let dst = buf.as_mut_ptr().add(9); + let dst = &mut buf[9..(9 + Hyphenated::LENGTH)]; + let dst: &mut [u8; Hyphenated::LENGTH] = dst.try_into().unwrap(); + *dst = format_hyphenated(src, upper); - ptr::write(dst.cast(), format_hyphenated(src, upper)); - str::from_utf8_unchecked_mut(buf) - } + // SAFETY: The encoded buffer is ASCII encoded + unsafe { str::from_utf8_unchecked_mut(buf) } } impl Hyphenated { diff --git a/src/lib.rs b/src/lib.rs index e42a075e..f7e016e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -438,16 +438,6 @@ pub enum Variant { #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] #[repr(transparent)] // NOTE: Also check `NonNilUuid` when ading new derives here -#[cfg_attr( - all(uuid_unstable, feature = "zerocopy"), - derive( - zerocopy::IntoBytes, - zerocopy::FromBytes, - zerocopy::KnownLayout, - zerocopy::Immutable, - zerocopy::Unaligned - ) -)] #[cfg_attr( feature = "borsh", derive(borsh_derive::BorshDeserialize, borsh_derive::BorshSerialize) @@ -456,6 +446,13 @@ pub enum Variant { feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod, bytemuck::TransparentWrapper) )] +#[derive( + zerocopy::IntoBytes, + zerocopy::FromBytes, + zerocopy::KnownLayout, + zerocopy::Immutable, + zerocopy::Unaligned, +)] pub struct Uuid(Bytes); impl Uuid { From 675cccc829fa8ce3f225392622aee1c41268b068 Mon Sep 17 00:00:00 2001 From: Ashley Mannix Date: Mon, 1 Sep 2025 07:18:26 +1000 Subject: [PATCH 2/2] re-gate zerocopy behind unstable feature flag --- Cargo.toml | 7 ++++- src/builder.rs | 2 +- src/fmt.rs | 77 ++++++++++++++++++++++++++++++++------------------ src/lib.rs | 21 ++++++++------ src/macros.rs | 30 ++++++++++++++++++++ 5 files changed, 98 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c397db0a..c0e920a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,8 +104,13 @@ version = "2" optional = true version = "1.1.3" -# Public: Used in trait impls on `Uuid` +# Public (unstable): Used in `zerocopy` derive +# Unstable: also need RUSTFLAGS="--cfg uuid_unstable" to work +# This feature may break between releases, or be removed entirely before +# stabilization. +# See: https://github.com/uuid-rs/uuid/issues/588 [dependencies.zerocopy] +optional = true version = "0.8" features = ["derive"] diff --git a/src/builder.rs b/src/builder.rs index 8af417f2..3fd4e66e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -431,7 +431,7 @@ impl Uuid { /// ``` #[inline] pub fn from_bytes_ref(bytes: &Bytes) -> &Uuid { - zerocopy::transmute_ref!(bytes) + unsafe_transmute_ref!(bytes) } // NOTE: There is no `from_u128_ref` because in little-endian diff --git a/src/fmt.rs b/src/fmt.rs index 86a35ec1..636cec6d 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -13,8 +13,6 @@ use core::{convert::TryInto as _, str::FromStr}; -use zerocopy::{transmute_ref, FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; - use crate::{ std::{borrow::Borrow, fmt, str}, Error, Uuid, Variant, @@ -79,11 +77,16 @@ impl fmt::UpperHex for Uuid { Ord, PartialEq, PartialOrd, - IntoBytes, - FromBytes, - KnownLayout, - Immutable, - Unaligned, +)] +#[cfg_attr( + all(uuid_unstable, feature = "zerocopy"), + derive( + zerocopy::IntoBytes, + zerocopy::FromBytes, + zerocopy::KnownLayout, + zerocopy::Immutable, + zerocopy::Unaligned + ) )] #[repr(transparent)] pub struct Hyphenated(Uuid); @@ -100,11 +103,16 @@ pub struct Hyphenated(Uuid); Ord, PartialEq, PartialOrd, - IntoBytes, - FromBytes, - KnownLayout, - Immutable, - Unaligned, +)] +#[cfg_attr( + all(uuid_unstable, feature = "zerocopy"), + derive( + zerocopy::IntoBytes, + zerocopy::FromBytes, + zerocopy::KnownLayout, + zerocopy::Immutable, + zerocopy::Unaligned + ) )] #[repr(transparent)] pub struct Simple(Uuid); @@ -121,11 +129,16 @@ pub struct Simple(Uuid); Ord, PartialEq, PartialOrd, - IntoBytes, - FromBytes, - KnownLayout, - Immutable, - Unaligned, +)] +#[cfg_attr( + all(uuid_unstable, feature = "zerocopy"), + derive( + zerocopy::IntoBytes, + zerocopy::FromBytes, + zerocopy::KnownLayout, + zerocopy::Immutable, + zerocopy::Unaligned + ) )] #[repr(transparent)] pub struct Urn(Uuid); @@ -142,11 +155,16 @@ pub struct Urn(Uuid); Ord, PartialEq, PartialOrd, - IntoBytes, - FromBytes, - KnownLayout, - Immutable, - Unaligned, +)] +#[cfg_attr( + all(uuid_unstable, feature = "zerocopy"), + derive( + zerocopy::IntoBytes, + zerocopy::FromBytes, + zerocopy::KnownLayout, + zerocopy::Immutable, + zerocopy::Unaligned + ) )] #[repr(transparent)] pub struct Braced(Uuid); @@ -161,7 +179,7 @@ impl Uuid { /// Get a borrowed [`Hyphenated`] formatter. #[inline] pub fn as_hyphenated(&self) -> &Hyphenated { - transmute_ref!(self) + unsafe_transmute_ref!(self) } /// Get a [`Simple`] formatter. @@ -173,7 +191,7 @@ impl Uuid { /// Get a borrowed [`Simple`] formatter. #[inline] pub fn as_simple(&self) -> &Simple { - transmute_ref!(self) + unsafe_transmute_ref!(self) } /// Get a [`Urn`] formatter. @@ -185,7 +203,7 @@ impl Uuid { /// Get a borrowed [`Urn`] formatter. #[inline] pub fn as_urn(&self) -> &Urn { - transmute_ref!(self) + unsafe_transmute_ref!(self) } /// Get a [`Braced`] formatter. @@ -197,7 +215,7 @@ impl Uuid { /// Get a borrowed [`Braced`] formatter. #[inline] pub fn as_braced(&self) -> &Braced { - transmute_ref!(self) + unsafe_transmute_ref!(self) } } @@ -274,7 +292,10 @@ fn encode_braced<'b>(src: &[u8; 16], buffer: &'b mut [u8], upper: bool) -> &'b m let buf = &mut buffer[..Hyphenated::LENGTH + 2]; let buf: &mut [u8; Hyphenated::LENGTH + 2] = buf.try_into().unwrap(); - #[derive(IntoBytes)] + #[cfg_attr( + all(uuid_unstable, feature = "zerocopy"), + derive(zerocopy::IntoBytes) + )] #[repr(C)] struct Braced { open_curly: u8, @@ -288,7 +309,7 @@ fn encode_braced<'b>(src: &[u8; 16], buffer: &'b mut [u8], upper: bool) -> &'b m close_curly: b'}', }; - *buf = zerocopy::transmute!(braced); + *buf = unsafe_transmute!(braced); // SAFETY: The encoded buffer is ASCII encoded unsafe { str::from_utf8_unchecked_mut(buf) } diff --git a/src/lib.rs b/src/lib.rs index f7e016e1..871675f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -223,6 +223,9 @@ extern crate std; #[macro_use] extern crate core as std; +#[macro_use] +mod macros; + mod builder; mod error; mod non_nil; @@ -267,9 +270,6 @@ mod sha1; mod external; -#[macro_use] -mod macros; - #[doc(hidden)] #[cfg(feature = "macro-diagnostics")] pub extern crate uuid_macro_internal; @@ -446,12 +446,15 @@ pub enum Variant { feature = "bytemuck", derive(bytemuck::Zeroable, bytemuck::Pod, bytemuck::TransparentWrapper) )] -#[derive( - zerocopy::IntoBytes, - zerocopy::FromBytes, - zerocopy::KnownLayout, - zerocopy::Immutable, - zerocopy::Unaligned, +#[cfg_attr( + all(uuid_unstable, feature = "zerocopy"), + derive( + zerocopy::IntoBytes, + zerocopy::FromBytes, + zerocopy::KnownLayout, + zerocopy::Immutable, + zerocopy::Unaligned + ) )] pub struct Uuid(Bytes); diff --git a/src/macros.rs b/src/macros.rs index 865c44dd..09924c04 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -86,3 +86,33 @@ define_uuid_macro! { /// /// [uuid::Uuid]: https://docs.rs/uuid/*/uuid/struct.Uuid.html } + +// Internal macros + +// These `transmute` macros are a stepping stone towards `zerocopy` integration. +// When the `zerocopy` feature is enabled, which it is in CI, the transmutes are +// checked by it + +// SAFETY: Callers must ensure this call would be safe when handled by zerocopy +#[cfg(not(all(uuid_unstable, feature = "zerocopy")))] +macro_rules! unsafe_transmute_ref( + ($e:expr) => { unsafe { core::mem::transmute::<&_, &_>($e) } } +); + +// SAFETY: Callers must ensure this call would be safe when handled by zerocopy +#[cfg(all(uuid_unstable, feature = "zerocopy"))] +macro_rules! unsafe_transmute_ref( + ($e:expr) => { zerocopy::transmute_ref!($e) } +); + +// SAFETY: Callers must ensure this call would be safe when handled by zerocopy +#[cfg(not(all(uuid_unstable, feature = "zerocopy")))] +macro_rules! unsafe_transmute( + ($e:expr) => { unsafe { core::mem::transmute::<_, _>($e) } } +); + +// SAFETY: Callers must ensure this call would be safe when handled by zerocopy +#[cfg(all(uuid_unstable, feature = "zerocopy"))] +macro_rules! unsafe_transmute( + ($e:expr) => { zerocopy::transmute!($e) } +);