From 808a829065c497e9e74648b0de5d11ffc6cd1173 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Tue, 17 Mar 2026 15:14:24 -1000 Subject: [PATCH 1/4] Add zerofrom::transparent! macro --- utils/zerofrom/src/lib.rs | 8 ++ utils/zerofrom/src/zf_transparent.rs | 95 ++++++++++++++++++++++++ utils/zerofrom/tests/test_transparent.rs | 29 ++++++++ 3 files changed, 132 insertions(+) create mode 100644 utils/zerofrom/src/zf_transparent.rs create mode 100644 utils/zerofrom/tests/test_transparent.rs diff --git a/utils/zerofrom/src/lib.rs b/utils/zerofrom/src/lib.rs index 680a64f3fc0..0c30df8dd83 100644 --- a/utils/zerofrom/src/lib.rs +++ b/utils/zerofrom/src/lib.rs @@ -28,8 +28,16 @@ extern crate alloc; mod macro_impls; mod zero_from; +mod zf_transparent; #[cfg(feature = "derive")] pub use zerofrom_derive::ZeroFrom; pub use crate::zero_from::ZeroFrom; + +#[cfg(feature = "alloc")] +#[doc(hidden)] // for macros +pub mod internal { + pub use alloc::boxed::Box; + pub use alloc::rc::Rc; +} diff --git a/utils/zerofrom/src/zf_transparent.rs b/utils/zerofrom/src/zf_transparent.rs new file mode 100644 index 00000000000..1991d95b29c --- /dev/null +++ b/utils/zerofrom/src/zf_transparent.rs @@ -0,0 +1,95 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +/// Implements [`ZeroFrom`](crate::ZeroFrom) on a transparent type +/// from a reference to the inner type. +/// +/// Also supports creating concrete functions. +/// +/// # Examples +/// +/// ``` +/// use crate::zerofrom::ZeroFrom; +/// +/// zerofrom::transparent!( +/// #[repr(transparent)] +/// pub struct StrWrap(str); +/// impl ZeroFrom<&str> for &StrWrap; +/// ); +/// +/// let s = "hello"; +/// let wrap = <&StrWrap>::zero_from(s); +/// +/// assert_eq!(&wrap.0, "hello"); +/// ``` +#[macro_export] +macro_rules! transparent { + ( + #[repr(transparent)] + $(#[$meta:meta])* + $vis:vis struct $name:ident($type:ty); + $( + impl ZeroFrom<&$type_zf:ty> for &$name_zf:ident; + )? + $(impl { + $( + @ref + $(#[$meta_ref:meta])* + $vis_ref:vis fn $fn_ref:ident(&$type_ref:ty) -> &Self; + )? + $( + @slice + $(#[$meta_slice:meta])* + $vis_slice:vis fn $fn_slice:ident(&[$type_slice:ty]) -> &[Self]; + )? + $( + @box + $(#[$meta_box:meta])* + $vis_box:vis fn $fn_box:ident(Box<$type_box:ty>) -> Box; + )? + $( + @rc + $(#[$meta_rc:meta])* + $vis_rc:vis fn $fn_rc:ident(Rc<$type_rc:ty>) -> Rc; + )? + })? + ) => { + #[repr(transparent)] + $(#[$meta])* + $vis struct $name($type); + $( + impl<'zf> $crate::ZeroFrom<'zf, $type_zf> for &'zf $name { + fn zero_from(inner: &'zf $type) -> Self { + unsafe { core::mem::transmute(inner) } + } + } + )? + $(impl $name { + $( + $(#[$meta_ref])* + $vis_ref fn $fn_ref(inner: &$type_ref) -> &Self { + unsafe { core::mem::transmute(inner) } + } + )? + $( + $(#[$meta_slice])* + $vis_slice fn $fn_slice(inner: &[$type_slice]) -> &[Self] { + unsafe { core::mem::transmute(inner) } + } + )? + $( + $(#[$meta_box])* + $vis_box fn $fn_box(inner: $crate::internal::Box<$type_box>) -> $crate::internal::Box { + unsafe { core::mem::transmute(inner) } + } + )? + $( + $(#[$meta_rc])* + $vis_rc fn $fn_rc(inner: $crate::internal::Rc<$type_rc>) -> $crate::internal::Rc { + unsafe { core::mem::transmute(inner) } + } + )? + })? + }; +} diff --git a/utils/zerofrom/tests/test_transparent.rs b/utils/zerofrom/tests/test_transparent.rs new file mode 100644 index 00000000000..bdaed96ef60 --- /dev/null +++ b/utils/zerofrom/tests/test_transparent.rs @@ -0,0 +1,29 @@ +// This file is part of ICU4X. For terms of use, please see the file +// called LICENSE at the top level of the ICU4X source tree +// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). + +use zerofrom::transparent; + +transparent!( + #[repr(transparent)] + /// hello world + #[derive(Debug)] + pub(crate) struct Foo([u8; 3]); + impl ZeroFrom<&[u8; 3]> for &Foo; + impl { + @ref + /// Cast a transparent ref! + #[inline] + fn from_transparent_ref(&[u8; 3]) -> &Self; + @slice + /// Cast a transparent slice! + pub fn from_transparent_slice(&[[u8; 3]]) -> &[Self]; + @box + /// Cast a transparent box! + #[cfg(feature = "alloc")] + fn from_transparent_box(Box<[u8; 3]>) -> Box; + } +); + +#[test] +fn test() {} From 30c49ef0d5a6cf1c48fc75ebdaee6d807c9cd515 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Tue, 14 Apr 2026 12:31:19 -1000 Subject: [PATCH 2/4] Improve organization of safety invariants --- utils/zerofrom/src/lib.rs | 1 + utils/zerofrom/src/zf_transparent.rs | 52 ++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/utils/zerofrom/src/lib.rs b/utils/zerofrom/src/lib.rs index 0c30df8dd83..e89071332e4 100644 --- a/utils/zerofrom/src/lib.rs +++ b/utils/zerofrom/src/lib.rs @@ -40,4 +40,5 @@ pub use crate::zero_from::ZeroFrom; pub mod internal { pub use alloc::boxed::Box; pub use alloc::rc::Rc; + pub use crate::zf_transparent::cast_transparent_box; } diff --git a/utils/zerofrom/src/zf_transparent.rs b/utils/zerofrom/src/zf_transparent.rs index 1991d95b29c..841c85bfbb5 100644 --- a/utils/zerofrom/src/zf_transparent.rs +++ b/utils/zerofrom/src/zf_transparent.rs @@ -2,6 +2,26 @@ // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +/// Internal function: casts a box of `Inner` to a box of `Outer`, assuming +/// `Outer` is transparent over `Inner`. +/// +/// # Safety +/// +/// `Outer` is `repr(transparent)` and has one non-zero-sized field +/// of type `Inner`. +#[cfg(feature = "alloc")] +pub unsafe fn cast_transparent_box(inner: Box) -> Box { + // Safety: + // + // - Both boxes have the same allocator (the global allocator). + // - Since `Outer` is transparent over `Inner`, they have the same layout. + // - `Box::into_raw` returns a properly aligned, non-null `*mut Inner`. + Box::from_raw(Box::into_raw(inner) as *mut Outer) +} + /// Implements [`ZeroFrom`](crate::ZeroFrom) on a transparent type /// from a reference to the inner type. /// @@ -28,65 +48,67 @@ macro_rules! transparent { ( #[repr(transparent)] $(#[$meta:meta])* - $vis:vis struct $name:ident($type:ty); + $vis:vis struct $outer:ident($inner:ty); $( - impl ZeroFrom<&$type_zf:ty> for &$name_zf:ident; + impl ZeroFrom<&$inner_zf:ty> for &$outer_zf:ident; )? $(impl { $( @ref $(#[$meta_ref:meta])* - $vis_ref:vis fn $fn_ref:ident(&$type_ref:ty) -> &Self; + $vis_ref:vis fn $fn_ref:ident(&$inner_ref:ty) -> &Self; )? $( @slice $(#[$meta_slice:meta])* - $vis_slice:vis fn $fn_slice:ident(&[$type_slice:ty]) -> &[Self]; + $vis_slice:vis fn $fn_slice:ident(&[$inner_slice:ty]) -> &[Self]; )? $( @box $(#[$meta_box:meta])* - $vis_box:vis fn $fn_box:ident(Box<$type_box:ty>) -> Box; + $vis_box:vis fn $fn_box:ident(Box<$inner_box:ty>) -> Box; )? $( @rc $(#[$meta_rc:meta])* - $vis_rc:vis fn $fn_rc:ident(Rc<$type_rc:ty>) -> Rc; + $vis_rc:vis fn $fn_rc:ident(Rc<$inner_rc:ty>) -> Rc; )? })? ) => { #[repr(transparent)] $(#[$meta])* - $vis struct $name($type); + $vis struct $outer($inner); $( - impl<'zf> $crate::ZeroFrom<'zf, $type_zf> for &'zf $name { - fn zero_from(inner: &'zf $type) -> Self { + impl<'zf> $crate::ZeroFrom<'zf, $inner_zf> for &'zf $outer { + fn zero_from(inner: &'zf $inner) -> Self { unsafe { core::mem::transmute(inner) } } } )? - $(impl $name { + $(impl $outer { $( $(#[$meta_ref])* - $vis_ref fn $fn_ref(inner: &$type_ref) -> &Self { + $vis_ref fn $fn_ref(inner: &$inner_ref) -> &Self { unsafe { core::mem::transmute(inner) } } )? $( $(#[$meta_slice])* - $vis_slice fn $fn_slice(inner: &[$type_slice]) -> &[Self] { + $vis_slice fn $fn_slice(inner: &[$inner_slice]) -> &[Self] { unsafe { core::mem::transmute(inner) } } )? $( $(#[$meta_box])* - $vis_box fn $fn_box(inner: $crate::internal::Box<$type_box>) -> $crate::internal::Box { - unsafe { core::mem::transmute(inner) } + $vis_box fn $fn_box(inner: $crate::internal::Box<$inner_box>) -> $crate::internal::Box { + // Safety: $outer is repr(transparent) over $inner. + // TODO: Enforce that $inner is the same as $inner_box + unsafe { $crate::internal::cast_transparent_box(inner) } } )? $( $(#[$meta_rc])* - $vis_rc fn $fn_rc(inner: $crate::internal::Rc<$type_rc>) -> $crate::internal::Rc { + $vis_rc fn $fn_rc(inner: $crate::internal::Rc<$inner_rc>) -> $crate::internal::Rc { unsafe { core::mem::transmute(inner) } } )? From 8e6fa42c80eb207315fcdb109dea646cb07cadf4 Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Tue, 14 Apr 2026 12:32:33 -1000 Subject: [PATCH 3/4] Always inline pointer-cast functions --- utils/zerofrom/src/zf_transparent.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/zerofrom/src/zf_transparent.rs b/utils/zerofrom/src/zf_transparent.rs index 841c85bfbb5..a7bb07016ea 100644 --- a/utils/zerofrom/src/zf_transparent.rs +++ b/utils/zerofrom/src/zf_transparent.rs @@ -13,6 +13,7 @@ use alloc::boxed::Box; /// `Outer` is `repr(transparent)` and has one non-zero-sized field /// of type `Inner`. #[cfg(feature = "alloc")] +#[inline(always)] pub unsafe fn cast_transparent_box(inner: Box) -> Box { // Safety: // From adf39cc61c11240627040afc5577419911cb017a Mon Sep 17 00:00:00 2001 From: "Shane F. Carr" Date: Tue, 14 Apr 2026 13:05:40 -1000 Subject: [PATCH 4/4] Improve safety --- utils/zerofrom/src/lib.rs | 2 +- utils/zerofrom/src/zf_transparent.rs | 55 ++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/utils/zerofrom/src/lib.rs b/utils/zerofrom/src/lib.rs index e89071332e4..31bb5728ca4 100644 --- a/utils/zerofrom/src/lib.rs +++ b/utils/zerofrom/src/lib.rs @@ -38,7 +38,7 @@ pub use crate::zero_from::ZeroFrom; #[cfg(feature = "alloc")] #[doc(hidden)] // for macros pub mod internal { + pub use crate::zf_transparent::cast_transparent_box; pub use alloc::boxed::Box; pub use alloc::rc::Rc; - pub use crate::zf_transparent::cast_transparent_box; } diff --git a/utils/zerofrom/src/zf_transparent.rs b/utils/zerofrom/src/zf_transparent.rs index a7bb07016ea..c872e3dbd92 100644 --- a/utils/zerofrom/src/zf_transparent.rs +++ b/utils/zerofrom/src/zf_transparent.rs @@ -10,11 +10,13 @@ use alloc::boxed::Box; /// /// # Safety /// -/// `Outer` is `repr(transparent)` and has one non-zero-sized field -/// of type `Inner`. +/// `Outer` is `repr(transparent)` and has one non-zero-sized field of type `Inner`. +/// +/// Suggestion: explicitly setting the generic parameters to two types satisfying this invariant +/// makes the fn safe to call. #[cfg(feature = "alloc")] #[inline(always)] -pub unsafe fn cast_transparent_box(inner: Box) -> Box { +pub unsafe fn cast_transparent_box(inner: Box) -> Box { // Safety: // // - Both boxes have the same allocator (the global allocator). @@ -82,7 +84,7 @@ macro_rules! transparent { $( impl<'zf> $crate::ZeroFrom<'zf, $inner_zf> for &'zf $outer { fn zero_from(inner: &'zf $inner) -> Self { - unsafe { core::mem::transmute(inner) } + unsafe { core::mem::transmute::<&$inner, &$outer>(inner) } } } )? @@ -90,21 +92,20 @@ macro_rules! transparent { $( $(#[$meta_ref])* $vis_ref fn $fn_ref(inner: &$inner_ref) -> &Self { - unsafe { core::mem::transmute(inner) } + unsafe { core::mem::transmute::<&$inner, &$outer>(inner) } } )? $( $(#[$meta_slice])* $vis_slice fn $fn_slice(inner: &[$inner_slice]) -> &[Self] { - unsafe { core::mem::transmute(inner) } + unsafe { core::mem::transmute::<&[$inner], &[$outer]>(inner) } } )? $( $(#[$meta_box])* $vis_box fn $fn_box(inner: $crate::internal::Box<$inner_box>) -> $crate::internal::Box { // Safety: $outer is repr(transparent) over $inner. - // TODO: Enforce that $inner is the same as $inner_box - unsafe { $crate::internal::cast_transparent_box(inner) } + unsafe { $crate::internal::cast_transparent_box::<$inner, $outer>(inner) } } )? $( @@ -116,3 +117,41 @@ macro_rules! transparent { })? }; } + +/// Additional tests for failure modes. +/// +/// ```compile_fail,E0053 +/// zerofrom::transparent! { +/// #[repr(transparent)] +/// pub struct Foo(String); +/// // Wrong types in these positions! +/// impl ZeroFrom<&Foo> for &String; +/// }; +/// ``` +/// +/// ```compile_fail,E0308 +/// zerofrom::transparent! { +/// #[repr(transparent)] +/// pub struct Foo(String); +/// impl { +/// @ref +/// // Wrong type in this position! +/// pub fn from(&Foo) -> &Self; +/// } +/// }; +/// ``` +/// +/// ```compile_fail,E0308 +/// zerofrom::transparent! { +/// #[repr(transparent)] +/// pub struct Foo(String); +/// impl { +/// @slice +/// // Wrong type in this position! +/// pub fn from(&[Foo]) -> &[Self]; +/// } +/// }; +/// ``` +/// +/// TODO: Rc +mod _tests {}