Repository: Lokathor/bytemuck
Pull request #259 (2024-07-29 20:47:46)

Add `bytemuck` attribute to `NoUninit` derive
Not sure if you didn't add this attribute here for a reason, but it works perfectly for re-exporting the crate when you do

(I can also add the attribute to the other `proc_macro_derive`s if you'd like). 




Patch:
diff --git a/Cargo.toml b/Cargo.toml
index dda25b9..ffa457b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "bytemuck"
 description = "A crate for mucking around with piles of bytes."
-version = "1.16.1"
+version = "1.16.3"
 authors = ["Lokathor <zefria@gmail.com>"]
 repository = "https://github.com/Lokathor/bytemuck"
 readme = "README.md"
diff --git a/changelog.md b/changelog.md
index 58f5af8..37bb0c9 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,17 @@
 # `bytemuck` changelog
 
+## 1.16.3
+
+* Fully described in https://github.com/Lokathor/bytemuck/pull/256, This makes
+  casting slices to/from ZST elements more consistent between the crate's core
+  module and other modules.
+
+## 1.16.2
+
+* Fixes potential UB where `BoxBytes` could attempt to free a dangling pointer
+  if the `Layout` is zero sized. This type was introduced in 1.14.1, so that
+  version and the others up to and including 1.16.1 are now yanked for safety.
+
 ## 1.16.1
 
 * **NOT SEMVER SUPPORTED:** Adds the  `nightly_float` Cargo feature. This
diff --git a/derive/src/lib.rs b/derive/src/lib.rs
index cfe6270..806a72f 100644
--- a/derive/src/lib.rs
+++ b/derive/src/lib.rs
@@ -221,7 +221,7 @@ pub fn derive_zeroable(
 /// - The enum must be explicit `#[repr(Int)]`, `#[repr(C)]`, or both
 /// - All variants must be fieldless
 /// - The enum must contain no generic parameters
-#[proc_macro_derive(NoUninit)]
+#[proc_macro_derive(NoUninit, attributes(bytemuck))]
 pub fn derive_no_uninit(
   input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
diff --git a/src/allocation.rs b/src/allocation.rs
index ae0dca1..bb4032f 100644
--- a/src/allocation.rs
+++ b/src/allocation.rs
@@ -178,7 +178,7 @@ pub fn try_cast_slice_box<A: NoUninit, B: AnyBitPattern>(
     {
       // If the size in bytes of the underlying buffer does not match an exact
       // multiple of the size of B, we cannot cast between them.
-      Err((PodCastError::SizeMismatch, input))
+      Err((PodCastError::OutputSliceWouldHaveSlop, input))
     } else {
       // Because the size is an exact multiple, we can now change the length
       // of the slice and recreate the Box
@@ -239,7 +239,7 @@ pub fn try_cast_vec<A: NoUninit, B: AnyBitPattern>(
       // length and capacity are valid under B, as we do not want to
       // change which bytes are considered part of the initialized slice
       // of the Vec
-      Err((PodCastError::SizeMismatch, input))
+      Err((PodCastError::OutputSliceWouldHaveSlop, input))
     } else {
       // Because the size is an exact multiple, we can now change the length and
       // capacity and recreate the Vec
@@ -431,7 +431,7 @@ pub fn try_cast_slice_rc<
     {
       // If the size in bytes of the underlying buffer does not match an exact
       // multiple of the size of B, we cannot cast between them.
-      Err((PodCastError::SizeMismatch, input))
+      Err((PodCastError::OutputSliceWouldHaveSlop, input))
     } else {
       // Because the size is an exact multiple, we can now change the length
       // of the slice and recreate the Rc
@@ -499,7 +499,7 @@ pub fn try_cast_slice_arc<
     {
       // If the size in bytes of the underlying buffer does not match an exact
       // multiple of the size of B, we cannot cast between them.
-      Err((PodCastError::SizeMismatch, input))
+      Err((PodCastError::OutputSliceWouldHaveSlop, input))
     } else {
       // Because the size is an exact multiple, we can now change the length
       // of the slice and recreate the Arc
@@ -742,8 +742,9 @@ impl<I: ?Sized, T: ?Sized + TransparentWrapper<I>> TransparentWrapperAlloc<I>
 
 /// As `Box<[u8]>`, but remembers the original alignment.
 pub struct BoxBytes {
-  // SAFETY: `ptr` is owned, was allocated with `layout`, and points to
-  // `layout.size()` initialized bytes.
+  // SAFETY: `ptr` is owned, points to `layout.size()` initialized bytes, and
+  // was allocated with `layout` (unless `layout.size() == 0` in which case it
+  // is dangling).
   ptr: NonNull<u8>,
   layout: Layout,
 }
@@ -770,8 +771,11 @@ impl DerefMut for BoxBytes {
 
 impl Drop for BoxBytes {
   fn drop(&mut self) {
-    // SAFETY: See type invariant.
-    unsafe { alloc::alloc::dealloc(self.ptr.as_ptr(), self.layout) };
+    if self.layout.size() != 0 {
+      // SAFETY: See type invariant: if `self.layout.size() != 0`, then
+      // `self.ptr` is owned and was allocated with `self.layout`.
+      unsafe { alloc::alloc::dealloc(self.ptr.as_ptr(), self.layout) };
+    }
   }
 }
 
@@ -846,13 +850,18 @@ impl<T: AnyBitPattern> sealed::FromBoxBytes for [T] {
     let single_layout = Layout::new::<T>();
     if bytes.layout.align() != single_layout.align() {
       Err((PodCastError::AlignmentMismatch, bytes))
-    } else if single_layout.size() == 0 {
-      Err((PodCastError::SizeMismatch, bytes))
-    } else if bytes.layout.size() % single_layout.size() != 0 {
+    } else if (single_layout.size() == 0 && bytes.layout.size() != 0)
+      || (single_layout.size() != 0
+        && bytes.layout.size() % single_layout.size() != 0)
+    {
       Err((PodCastError::OutputSliceWouldHaveSlop, bytes))
     } else {
       let (ptr, layout) = bytes.into_raw_parts();
-      let length = layout.size() / single_layout.size();
+      let length = if single_layout.size() != 0 {
+        layout.size() / single_layout.size()
+      } else {
+        0
+      };
       let ptr =
         core::ptr::slice_from_raw_parts_mut(ptr.as_ptr() as *mut T, length);
       // SAFETY: See BoxBytes type invariant.
diff --git a/src/checked.rs b/src/checked.rs
index cb8c4c3..3299ce8 100644
--- a/src/checked.rs
+++ b/src/checked.rs
@@ -368,8 +368,6 @@ pub fn try_cast_mut<
 ///   type, and the output slice wouldn't be a whole number of elements when
 ///   accounting for the size change (eg: 3 `u16` values is 1.5 `u32` values, so
 ///   that's a failure).
-/// * Similarly, you can't convert between a [ZST](https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts)
-///   and a non-ZST.
 /// * If any element of the converted slice would contain an invalid bit pattern
 ///   for `B` this fails.
 #[inline]
diff --git a/src/internal.rs b/src/internal.rs
index 3ede50f..06935e6 100644
--- a/src/internal.rs
+++ b/src/internal.rs
@@ -51,13 +51,9 @@ pub(crate) fn something_went_wrong<D>(_src: &str, _err: D) -> ! {
 /// empty slice might not match the pointer value of the input reference.
 #[inline(always)]
 pub(crate) unsafe fn bytes_of<T: Copy>(t: &T) -> &[u8] {
-  if size_of::<T>() == 0 {
-    &[]
-  } else {
-    match try_cast_slice::<T, u8>(core::slice::from_ref(t)) {
-      Ok(s) => s,
-      Err(_) => unreachable!(),
-    }
+  match try_cast_slice::<T, u8>(core::slice::from_ref(t)) {
+    Ok(s) => s,
+    Err(_) => unreachable!(),
   }
 }
 
@@ -67,13 +63,9 @@ pub(crate) unsafe fn bytes_of<T: Copy>(t: &T) -> &[u8] {
 /// empty slice might not match the pointer value of the input reference.
 #[inline]
 pub(crate) unsafe fn bytes_of_mut<T: Copy>(t: &mut T) -> &mut [u8] {
-  if size_of::<T>() == 0 {
-    &mut []
-  } else {
-    match try_cast_slice_mut::<T, u8>(core::slice::from_mut(t)) {
-      Ok(s) => s,
-      Err(_) => unreachable!(),
-    }
+  match try_cast_slice_mut::<T, u8>(core::slice::from_mut(t)) {
+    Ok(s) => s,
+    Err(_) => unreachable!(),
   }
 }
 
@@ -347,12 +339,11 @@ pub(crate) unsafe fn try_cast_mut<A: Copy, B: Copy>(
 ///   type, and the output slice wouldn't be a whole number of elements when
 ///   accounting for the size change (eg: 3 `u16` values is 1.5 `u32` values, so
 ///   that's a failure).
-/// * Similarly, you can't convert between a [ZST](https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts)
-///   and a non-ZST.
 #[inline]
 pub(crate) unsafe fn try_cast_slice<A: Copy, B: Copy>(
   a: &[A],
 ) -> Result<&[B], PodCastError> {
+  let input_bytes = core::mem::size_of_val::<[A]>(a);
   // Note(Lokathor): everything with `align_of` and `size_of` will optimize away
   // after monomorphization.
   if align_of::<B>() > align_of::<A>()
@@ -361,10 +352,11 @@ pub(crate) unsafe fn try_cast_slice<A: Copy, B: Copy>(
     Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
   } else if size_of::<B>() == size_of::<A>() {
     Ok(unsafe { core::slice::from_raw_parts(a.as_ptr() as *const B, a.len()) })
-  } else if size_of::<A>() == 0 || size_of::<B>() == 0 {
-    Err(PodCastError::SizeMismatch)
-  } else if core::mem::size_of_val(a) % size_of::<B>() == 0 {
-    let new_len = core::mem::size_of_val(a) / size_of::<B>();
+  } else if (size_of::<B>() != 0 && input_bytes % size_of::<B>() == 0)
+    || (size_of::<B>() == 0 && input_bytes == 0)
+  {
+    let new_len =
+      if size_of::<B>() != 0 { input_bytes / size_of::<B>() } else { 0 };
     Ok(unsafe { core::slice::from_raw_parts(a.as_ptr() as *const B, new_len) })
   } else {
     Err(PodCastError::OutputSliceWouldHaveSlop)
@@ -379,6 +371,7 @@ pub(crate) unsafe fn try_cast_slice<A: Copy, B: Copy>(
 pub(crate) unsafe fn try_cast_slice_mut<A: Copy, B: Copy>(
   a: &mut [A],
 ) -> Result<&mut [B], PodCastError> {
+  let input_bytes = core::mem::size_of_val::<[A]>(a);
   // Note(Lokathor): everything with `align_of` and `size_of` will optimize away
   // after monomorphization.
   if align_of::<B>() > align_of::<A>()
@@ -389,10 +382,11 @@ pub(crate) unsafe fn try_cast_slice_mut<A: Copy, B: Copy>(
     Ok(unsafe {
       core::slice::from_raw_parts_mut(a.as_mut_ptr() as *mut B, a.len())
     })
-  } else if size_of::<A>() == 0 || size_of::<B>() == 0 {
-    Err(PodCastError::SizeMismatch)
-  } else if core::mem::size_of_val(a) % size_of::<B>() == 0 {
-    let new_len = core::mem::size_of_val(a) / size_of::<B>();
+  } else if (size_of::<B>() != 0 && input_bytes % size_of::<B>() == 0)
+    || (size_of::<B>() == 0 && input_bytes == 0)
+  {
+    let new_len =
+      if size_of::<B>() != 0 { input_bytes / size_of::<B>() } else { 0 };
     Ok(unsafe {
       core::slice::from_raw_parts_mut(a.as_mut_ptr() as *mut B, new_len)
     })
diff --git a/src/lib.rs b/src/lib.rs
index 1526587..dfb8ae8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -198,15 +198,14 @@ pub use bytemuck_derive::{
 /// The things that can go wrong when casting between [`Pod`] data forms.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum PodCastError {
-  /// You tried to cast a slice to an element type with a higher alignment
-  /// requirement but the slice wasn't aligned.
+  /// You tried to cast a reference into a reference to a type with a higher alignment
+  /// requirement but the input reference wasn't aligned.
   TargetAlignmentGreaterAndInputNotAligned,
-  /// If the element size changes then the output slice changes length
-  /// accordingly. If the output slice wouldn't be a whole number of elements
+  /// If the element size of a slice changes, then the output slice changes length
+  /// accordingly. If the output slice wouldn't be a whole number of elements,
   /// then the conversion fails.
   OutputSliceWouldHaveSlop,
-  /// When casting a slice you can't convert between ZST elements and non-ZST
-  /// elements. When casting an individual `T`, `&T`, or `&mut T` value the
+  /// When casting an individual `T`, `&T`, or `&mut T` value the
   /// source size and destination size must be an exact match.
   SizeMismatch,
   /// For this type of cast the alignments must be exactly the same and they
diff --git a/src/must.rs b/src/must.rs
index 96661be..4c8c613 100644
--- a/src/must.rs
+++ b/src/must.rs
@@ -11,9 +11,9 @@ impl<A, B> Cast<A, B> {
   const ASSERT_ALIGN_GREATER_THAN_EQUAL: () =
     assert!(align_of::<A>() >= align_of::<B>());
   const ASSERT_SIZE_EQUAL: () = assert!(size_of::<A>() == size_of::<B>());
-  const ASSERT_SIZE_MULTIPLE_OF: () = assert!(
-    (size_of::<A>() == 0) == (size_of::<B>() == 0)
-      && (size_of::<A>() % size_of::<B>() == 0)
+  const ASSERT_SIZE_MULTIPLE_OF_OR_INPUT_ZST: () = assert!(
+    (size_of::<A>() == 0)
+      || (size_of::<B>() != 0 && size_of::<A>() % size_of::<B>() == 0)
   );
 }
 
@@ -113,8 +113,8 @@ pub fn must_cast_mut<
 /// * If the target type has a greater alignment requirement.
 /// * If the target element type doesn't evenly fit into the the current element
 ///   type (eg: 3 `u16` values is 1.5 `u32` values, so that's a failure).
-/// * Similarly, you can't convert between a [ZST](https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts)
-///   and a non-ZST.
+/// * Similarly, you can't convert from a non-[ZST](https://doc.rust-lang.org/nomicon/exotic-sizes.html#zero-sized-types-zsts)
+///   to a ZST (e.g. 3 `u8` values is not any number of `()` values).
 ///
 /// ## Examples
 /// ```
@@ -122,6 +122,11 @@ pub fn must_cast_mut<
 /// // compiles:
 /// let bytes: &[u8] = bytemuck::must_cast_slice(indicies);
 /// ```
+/// ```
+/// let zsts: &[()] = &[(), (), ()];
+/// // compiles:
+/// let bytes: &[u8] = bytemuck::must_cast_slice(zsts);
+/// ```
 /// ```compile_fail,E0080
 /// # let bytes : &[u8] = &[1, 0, 2, 0, 3, 0];
 /// // fails to compile (bytes.len() might not be a multiple of 2):
@@ -132,9 +137,14 @@ pub fn must_cast_mut<
 /// // fails to compile (alignment requirements increased):
 /// let indicies : &[u16] = bytemuck::must_cast_slice(byte_pairs);
 /// ```
+/// ```compile_fail,E0080
+/// let bytes: &[u8] = &[];
+/// // fails to compile: (bytes.len() might not be 0)
+/// let zsts: &[()] = bytemuck::must_cast_slice(bytes);
+/// ```
 #[inline]
 pub fn must_cast_slice<A: NoUninit, B: AnyBitPattern>(a: &[A]) -> &[B] {
-  let _ = Cast::<A, B>::ASSERT_SIZE_MULTIPLE_OF;
+  let _ = Cast::<A, B>::ASSERT_SIZE_MULTIPLE_OF_OR_INPUT_ZST;
   let _ = Cast::<A, B>::ASSERT_ALIGN_GREATER_THAN_EQUAL;
   let new_len = if size_of::<A>() == size_of::<B>() {
     a.len()
@@ -156,6 +166,11 @@ pub fn must_cast_slice<A: NoUninit, B: AnyBitPattern>(a: &[A]) -> &[B] {
 /// // compiles:
 /// let bytes: &mut [u8] = bytemuck::must_cast_slice_mut(indicies);
 /// ```
+/// ```
+/// let zsts: &mut [()] = &mut [(), (), ()];
+/// // compiles:
+/// let bytes: &mut [u8] = bytemuck::must_cast_slice_mut(zsts);
+/// ```
 /// ```compile_fail,E0080
 /// # let mut bytes = [1, 0, 2, 0, 3, 0];
 /// # let bytes : &mut [u8] = &mut bytes[..];
@@ -168,6 +183,11 @@ pub fn must_cast_slice<A: NoUninit, B: AnyBitPattern>(a: &[A]) -> &[B] {
 /// // fails to compile (alignment requirements increased):
 /// let indicies : &mut [u16] = bytemuck::must_cast_slice_mut(byte_pairs);
 /// ```
+/// ```compile_fail,E0080
+/// let bytes: &mut [u8] = &mut [];
+/// // fails to compile: (bytes.len() might not be 0)
+/// let zsts: &mut [()] = bytemuck::must_cast_slice_mut(bytes);
+/// ```
 #[inline]
 pub fn must_cast_slice_mut<
   A: NoUninit + AnyBitPattern,
@@ -175,7 +195,7 @@ pub fn must_cast_slice_mut<
 >(
   a: &mut [A],
 ) -> &mut [B] {
-  let _ = Cast::<A, B>::ASSERT_SIZE_MULTIPLE_OF;
+  let _ = Cast::<A, B>::ASSERT_SIZE_MULTIPLE_OF_OR_INPUT_ZST;
   let _ = Cast::<A, B>::ASSERT_ALIGN_GREATER_THAN_EQUAL;
   let new_len = if size_of::<A>() == size_of::<B>() {
     a.len()


Test patch:
diff --git a/tests/cast_slice_tests.rs b/tests/cast_slice_tests.rs
index 71bc4df..7dbb45e 100644
--- a/tests/cast_slice_tests.rs
+++ b/tests/cast_slice_tests.rs
@@ -196,6 +196,30 @@ fn test_panics() {
   should_panic!(from_bytes::<u32>(&aligned_bytes[1..5]));
 }
 
+#[test]
+fn test_zsts() {
+  #[derive(Debug, Clone, Copy)]
+  struct MyZst;
+  unsafe impl Zeroable for MyZst {}
+  unsafe impl Pod for MyZst {}
+  assert_eq!(42, cast_slice::<(), MyZst>(&[(); 42]).len());
+  assert_eq!(42, cast_slice_mut::<(), MyZst>(&mut [(); 42]).len());
+  assert_eq!(0, cast_slice::<(), u8>(&[(); 42]).len());
+  assert_eq!(0, cast_slice_mut::<(), u8>(&mut [(); 42]).len());
+  assert_eq!(0, cast_slice::<u8, ()>(&[]).len());
+  assert_eq!(0, cast_slice_mut::<u8, ()>(&mut []).len());
+
+  assert_eq!(
+    PodCastError::OutputSliceWouldHaveSlop,
+    try_cast_slice::<u8, ()>(&[42]).unwrap_err()
+  );
+
+  assert_eq!(
+    PodCastError::OutputSliceWouldHaveSlop,
+    try_cast_slice_mut::<u8, ()>(&mut [42]).unwrap_err()
+  );
+}
+
 #[cfg(feature = "extern_crate_alloc")]
 #[test]
 fn test_boxed_slices() {
@@ -209,7 +233,6 @@ fn test_boxed_slices() {
     result.expect_err("u16 and i8 have different alignment");
   assert_eq!(error, PodCastError::AlignmentMismatch);
 
-  // FIXME(#253): Should these next two casts' errors be consistent?
   let result: Result<&[[i8; 3]], PodCastError> =
     try_cast_slice(&*boxed_i8_slice);
   let error =
@@ -220,7 +243,7 @@ fn test_boxed_slices() {
     try_cast_slice_box(boxed_i8_slice);
   let (error, boxed_i8_slice) =
     result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
-  assert_eq!(error, PodCastError::SizeMismatch);
+  assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
 
   let empty: Box<[()]> = cast_slice_box::<u8, ()>(Box::new([]));
   assert!(empty.is_empty());
@@ -229,7 +252,7 @@ fn test_boxed_slices() {
     try_cast_slice_box(boxed_i8_slice);
   let (error, boxed_i8_slice) =
     result.expect_err("slice of ZST cannot be made from slice of 4 u8s");
-  assert_eq!(error, PodCastError::SizeMismatch);
+  assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
 
   drop(boxed_i8_slice);
 
@@ -254,7 +277,6 @@ fn test_rc_slices() {
     result.expect_err("u16 and i8 have different alignment");
   assert_eq!(error, PodCastError::AlignmentMismatch);
 
-  // FIXME(#253): Should these next two casts' errors be consistent?
   let result: Result<&[[i8; 3]], PodCastError> = try_cast_slice(&*rc_i8_slice);
   let error =
     result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
@@ -264,7 +286,7 @@ fn test_rc_slices() {
     try_cast_slice_rc(rc_i8_slice);
   let (error, rc_i8_slice) =
     result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
-  assert_eq!(error, PodCastError::SizeMismatch);
+  assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
 
   let empty: Rc<[()]> = cast_slice_rc::<u8, ()>(Rc::new([]));
   assert!(empty.is_empty());
@@ -273,7 +295,7 @@ fn test_rc_slices() {
     try_cast_slice_rc(rc_i8_slice);
   let (error, rc_i8_slice) =
     result.expect_err("slice of ZST cannot be made from slice of 4 u8s");
-  assert_eq!(error, PodCastError::SizeMismatch);
+  assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
 
   drop(rc_i8_slice);
 
@@ -299,7 +321,6 @@ fn test_arc_slices() {
     result.expect_err("u16 and i8 have different alignment");
   assert_eq!(error, PodCastError::AlignmentMismatch);
 
-  // FIXME(#253): Should these next two casts' errors be consistent?
   let result: Result<&[[i8; 3]], PodCastError> = try_cast_slice(&*arc_i8_slice);
   let error =
     result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
@@ -309,7 +330,7 @@ fn test_arc_slices() {
     try_cast_slice_arc(arc_i8_slice);
   let (error, arc_i8_slice) =
     result.expect_err("slice of [i8; 3] cannot be made from slice of 4 i8s");
-  assert_eq!(error, PodCastError::SizeMismatch);
+  assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
 
   let empty: Arc<[()]> = cast_slice_arc::<u8, ()>(Arc::new([]));
   assert!(empty.is_empty());
@@ -318,7 +339,7 @@ fn test_arc_slices() {
     try_cast_slice_arc(arc_i8_slice);
   let (error, arc_i8_slice) =
     result.expect_err("slice of ZST cannot be made from slice of 4 u8s");
-  assert_eq!(error, PodCastError::SizeMismatch);
+  assert_eq!(error, PodCastError::OutputSliceWouldHaveSlop);
 
   drop(arc_i8_slice);
 
@@ -328,3 +349,23 @@ fn test_arc_slices() {
   let empty: Arc<[i8]> = cast_slice_arc::<(), i8>(Arc::new([(); 42]));
   assert!(empty.is_empty());
 }
+
+#[cfg(feature = "extern_crate_alloc")]
+#[test]
+fn box_bytes_zst() {
+  let x: BoxBytes = box_bytes_of(Box::new([0u8; 0]));
+  let _: Box<[u8]> = from_box_bytes(x);
+
+  let x: BoxBytes = box_bytes_of(Box::new([0u8; 0]));
+  let _: Box<[()]> = from_box_bytes(x);
+
+  let x: BoxBytes = box_bytes_of(Box::new([(); 0]));
+  let _: Box<[u8]> = from_box_bytes(x);
+
+  let x: BoxBytes = box_bytes_of(Box::new([0u8]));
+  let res: Result<Box<[()]>, _> = try_from_box_bytes(x);
+  assert_eq!(res.unwrap_err().0, PodCastError::OutputSliceWouldHaveSlop);
+
+  // regression test for dropping zero-sized BoxBytes
+  let _: BoxBytes = box_bytes_of(Box::new([0u8; 0]));
+}