Repository: Lokathor/bytemuck (rust)

Issue:
Zero-sized type (ZST) handling inconsistency between borrowed and owned slice casts.
(For the purposes of this issue, assume `Src`/`Dst`/`Zst`/`NonZst` have the same alignment. Borrowed and owned casts intentionally have different alignment requirements, which are not the issue here.)

`bytemuck::try_cast_slice<Src, Dst>`[^2] [is documented as](https://docs.rs/bytemuck/1.16.1/bytemuck/fn.try_cast_slice.html) returning an error (specifically `Err(SizeMismatch)`) when `Src` and `Dst` are a ZST and a non-ZST (in either order).

However, `bytemuck::allocation::try_cast_slice_box<Src, Dst>`[^1]  says nothing in [its documentation](https://docs.rs/bytemuck/1.16.1/bytemuck/allocation/fn.try_cast_slice_box.html) regarding ZSTs, currently always returns `Ok(empty)` when `Src` is a ZST and `Dst` is not, and currently *panics* with a modulo-by-zero error when `Src` is a non-ZST and `Dst` is a ZST. ([playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0cda1505fa555be47eb31149dcaeacb0))

I see three possible overall resolutions to the inconsistency:

1. Keep (and document?) the inconsistency (but fix the panic).
    * `try_cast_slice_box` allows `[Zst] -> [NonZst]`, returning an empty slice.
    * `try_cast_slice` disallows `[Zst] -> [NonZst]` and `[NonZst] -> [Zst]`, returning `Err(SizeMismatch)`
    * Status quo
2. Make `cast_slice` behave like `cast_slice_box` (but fix the panic).
    * `cast_slice` and `cast_slice_box` allow `[Zst] -> [NonZst]` casts, returning an empty slice.
    * May break code relying on `cast_slice::<Zst, NonZst>` not succeeding.
3. Make `cast_slice_box` behave like `cast_slice`.
    * Disallow any casting between non-ZST <-> ZST slices, returning `Err(SizeMismatch)`.
    * May break code relying on `cast_vec::<Zst, NonZst>` succeeding.
    * (Implies option 2 below)
    
and two possible resolutions for the `[NonZst] -> [Zst]` panic:

1. Allow empty source slices for `[NonZst] -> [Zst]`, return an error[^3] on non-empty source slices.
    * This matches the existing documentation that the start and end content size must be the same.
2. Disallow casting from `[NonZst]` to `[Zst]`, always return an error[^3].

[^1]: Everything mentioned about `cast_slice_box` also applies to `cast_vec`, `cast_slice_arc`, and `cast_slice_rc`.
[^2]: Everything mentioned about `cast_slice` also applies to `cast_slice_mut`)
[^3]: On non-empty source slices, could return `Err(SizeMismatch)`, or could return `Err(OutputSliceWouldHaveSlop)` ("If the output slice wouldn’t be a whole number of elements then the conversion fails.") since there is no whole number of ZSTs that would have any non-zero size, and on empty source slices since *any* whole number of ZSTs would have zero size.

Pull request description:
Allow casting between slices of ZSTs and slices of non-ZSTs in all cases.
Closes #253.

> having ZST slices turn into 0-length non-ZST slices does seem like a compelling path.

This PR implements that. Additionally, casting non-ZST slices to ZST slices will work iff the input slice has length 0, and results in a slice length of 0; if the input slice is not of length 0, `PodCastError::OutputSliceWouldHaveSlop` is returned.

Updates the docs of the `PodCastError` variants to reflect when they can occur.
Updates the docs of `try_cast_slice` (and `checked::`) to remove note about ZST <-> non-ZST not being allowed.
Update `bytes_of(_mut)` to remove ZST-may-not-have-same-address exception, since casting [ZST] -> [u8] is now allowed directly using `cast_slice(_mut)` (but does not remove the documentation of the exception just in case).
Update `must_cast_slice` checks and doctests to allow [ZST] -> [non-ZST], but disallow [non-ZST] -> [ZST].

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index dda25b9..0ccf3fa 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.2"
 authors = ["Lokathor <zefria@gmail.com>"]
 repository = "https://github.com/Lokathor/bytemuck"
 readme = "README.md"
diff --git a/changelog.md b/changelog.md
index 58f5af8..69164b4 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,11 @@
 # `bytemuck` changelog
 
+## 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/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..0f94f0b 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,17 @@ 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 res: Result<Box<[()]>, _> = try_from_box_bytes(x);
+  assert_eq!(res.unwrap_err().0, PodCastError::SizeMismatch);
+
+  let x: BoxBytes = box_bytes_of(Box::new([(); 0]));
+  let _: Box<[u8]> = from_box_bytes(x);
+}