diff --git a/utils/zerofrom/src/lib.rs b/utils/zerofrom/src/lib.rs index 680a64f3fc0..31bb5728ca4 100644 --- a/utils/zerofrom/src/lib.rs +++ b/utils/zerofrom/src/lib.rs @@ -28,8 +28,17 @@ 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 crate::zf_transparent::cast_transparent_box; + 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..c872e3dbd92 --- /dev/null +++ b/utils/zerofrom/src/zf_transparent.rs @@ -0,0 +1,157 @@ +// 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 ). + +#[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`. +/// +/// 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 { + // 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. +/// +/// 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 $outer:ident($inner:ty); + $( + impl ZeroFrom<&$inner_zf:ty> for &$outer_zf:ident; + )? + $(impl { + $( + @ref + $(#[$meta_ref:meta])* + $vis_ref:vis fn $fn_ref:ident(&$inner_ref:ty) -> &Self; + )? + $( + @slice + $(#[$meta_slice:meta])* + $vis_slice:vis fn $fn_slice:ident(&[$inner_slice:ty]) -> &[Self]; + )? + $( + @box + $(#[$meta_box:meta])* + $vis_box:vis fn $fn_box:ident(Box<$inner_box:ty>) -> Box; + )? + $( + @rc + $(#[$meta_rc:meta])* + $vis_rc:vis fn $fn_rc:ident(Rc<$inner_rc:ty>) -> Rc; + )? + })? + ) => { + #[repr(transparent)] + $(#[$meta])* + $vis struct $outer($inner); + $( + impl<'zf> $crate::ZeroFrom<'zf, $inner_zf> for &'zf $outer { + fn zero_from(inner: &'zf $inner) -> Self { + unsafe { core::mem::transmute::<&$inner, &$outer>(inner) } + } + } + )? + $(impl $outer { + $( + $(#[$meta_ref])* + $vis_ref fn $fn_ref(inner: &$inner_ref) -> &Self { + unsafe { core::mem::transmute::<&$inner, &$outer>(inner) } + } + )? + $( + $(#[$meta_slice])* + $vis_slice fn $fn_slice(inner: &[$inner_slice]) -> &[Self] { + 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. + unsafe { $crate::internal::cast_transparent_box::<$inner, $outer>(inner) } + } + )? + $( + $(#[$meta_rc])* + $vis_rc fn $fn_rc(inner: $crate::internal::Rc<$inner_rc>) -> $crate::internal::Rc { + unsafe { core::mem::transmute(inner) } + } + )? + })? + }; +} + +/// 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 {} 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() {}