Repository: Lokathor/bytemuck
Pull request #137 (2022-10-23 11:15:57)

Fix clippy lints
- allows multiple clippy lints that fire from the derives in `bytemuck`

Lokathor: @andrewgazelka it looks like some of your changes were too aggressive because of the MSRV. For those parts, just silence the clippy lint if you like.

Patch:
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..9d8a446
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,4 @@
+[alias]
+
+# The list of features should be the same as the one under `[package.metadata.docs.rs]`
+nightly_docs = "doc --no-deps -F nightly_docs,derive,extern_crate_alloc,extern_crate_std,zeroable_maybe_uninit,zeroable_atomics,min_const_generics,wasm_simd,must_cast"
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 8abac7e..19ae2c5 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -32,7 +32,7 @@ jobs:
     - uses: hecrj/setup-rust-action@v1
       with:
         rust-version: ${{ matrix.rust }}
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - run: cargo test --verbose
     - run: cargo test --verbose --no-default-features
     - run: cargo test --verbose
@@ -46,13 +46,13 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        # note: the mips targets are here so that we have big-endian coverage (both 32bit and 64bit)
-        target: [i686-unknown-linux-gnu, mips-unknown-linux-gnu, mips64-unknown-linux-gnuabi64]
+        # we once had mips runners for Big-endian coverage but those got demoted to tier 3.
+        target: [i686-unknown-linux-gnu]
     steps:
     - uses: hecrj/setup-rust-action@v1
       with:
         rust-version: nightly
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - run: cargo install cross
     - run: cross test --verbose --target=${{ matrix.target }} --no-default-features
     - run: cross test --verbose --target=${{ matrix.target }}
@@ -64,14 +64,12 @@ jobs:
   miri-test:
     name: Test with miri
     runs-on: ubuntu-latest
-    env:
-      MIRIFLAGS: -Zmiri-tag-raw-pointers
     steps:
     - uses: hecrj/setup-rust-action@v1
       with:
         rust-version: nightly
         components: miri
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     # Note(Lokathor): We got some cached json errors, and so we cargo clean for this run.
     - run: rm -fr target
     - run: cargo miri test --verbose --no-default-features
@@ -86,7 +84,7 @@ jobs:
       matrix:
         sanitizer: [address, memory, leak]
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - uses: hecrj/setup-rust-action@v1
       with:
         rust-version: nightly
diff --git a/.gitignore b/.gitignore
index f939153..fb74370 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ Cargo.lock
 **/*.rs.bk
 
 /derive/target/
+/derive/.vscode/
diff --git a/Cargo.toml b/Cargo.toml
index 5b7e1cf..05a2715 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.12.1"
+version = "1.16.0"
 authors = ["Lokathor <zefria@gmail.com>"]
 repository = "https://github.com/Lokathor/bytemuck"
 readme = "README.md"
@@ -17,28 +17,54 @@ derive = ["bytemuck_derive"]
 extern_crate_alloc = []
 extern_crate_std = ["extern_crate_alloc"]
 zeroable_maybe_uninit = []
-min_const_generics = []
-wasm_simd = [] # Until >= 1.54.0 is MSRV this is an off-by-default feature.
-aarch64_simd = [] # Until >= 1.59.0 is MSRV this is an off-by-default feature.
+zeroable_atomics = []
 
-# Do not use if you can avoid it, because this is unsound.
+# All MSRV notes below are GUIDELINES and future versions may require even more
+# MSRV on any feature.
+
+# MSRV 1.36: Use `align_offset` method instead of casting to `usize` to check
+# alignment of pointers, this *may* improve codegen in some cases (but it has
+# never been formally benchmarked!)
+align_offset = []
+
+min_const_generics = [] # MSRV 1.51: support arrays via min_const_generics
+
+wasm_simd = []    # MSRV 1.54.0: support wasm simd types
+aarch64_simd = [] # MSRV 1.59.0: support aarch64 simd types
+
+must_cast = [] # MSRV 1.57.0: support the `must` module.
+
+const_zeroed = [] # MSRV 1.75.0: support const `zeroed()`
+
+# Do not use if you can avoid it, because this is **unsound**!!!!
 unsound_ptr_pod_impl = []
 
 # NOT SEMVER SUPPORTED! TEMPORARY ONLY!
 nightly_portable_simd = []
+nightly_stdsimd = []
+
+# Improved documentation using the nightly toolchain
+nightly_docs = []
 
 [dependencies]
-bytemuck_derive = { version = "1.2.1", path = "derive", optional = true }
+bytemuck_derive = { version = "1.4", path = "derive", optional = true }
+
+[lints.rust]
+unexpected_cfgs = { level = "deny", check-cfg = ['cfg(target_arch, values("spirv"))'] }
 
 [package.metadata.docs.rs]
 # Note(Lokathor): Don't use all-features or it would use `unsound_ptr_pod_impl` too.
 features = [
+  "nightly_docs",
   "derive",
   "extern_crate_alloc",
   "extern_crate_std",
   "zeroable_maybe_uninit",
+  "zeroable_atomics",
   "min_const_generics",
   "wasm_simd",
+  "must_cast",
+  "const_zeroed",
 ]
 
 [package.metadata.playground]
@@ -48,6 +74,9 @@ features = [
   "extern_crate_alloc",
   "extern_crate_std",
   "zeroable_maybe_uninit",
+  "zeroable_atomics",
   "min_const_generics",
   "wasm_simd",
+  "must_cast",
+  "const_zeroed",
 ]
diff --git a/README.md b/README.md
index 5a5e85f..7b91a05 100644
--- a/README.md
+++ b/README.md
@@ -46,10 +46,10 @@ operations that are 100% safe will be added.
 * The crate is 1.0 and I consider this it to be "basically done". New features
   are usually being accepted when other people want to put in the work, but
   myself I wanna move on to using `bytemuck` in bigger projects.
-* The core of the `bytemuck` crate will continue to work with `rustc-1.34` for
-  at least the rest of the `1.y.z` versions.
-* Opt-in features of the crate *are not* held to the same standard, and may work
-  only on the latest Stable or latest Nightly.
+* The default build of the `bytemuck` crate will continue to work with `rustc-1.34`
+  for at least the rest of the `1.y.z` versions.
+* Any other cargo features of the crate **are not** held to the same standard, and
+  may work only on the latest Stable or even only on latest Nightly.
 
 **Future Plans:** Once the [Safe Transmute Project][pg-st] completes and
 stabilizes ("eventually") this crate will be updated to use that as the
diff --git a/changelog.md b/changelog.md
index a39989f..66f0cec 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,90 @@
 # `bytemuck` changelog
 
+## 1.16.0
+
+* Adds a `const_zeroed` feature (MSRV 1.75) which puts a `zeroed` fn at the crate root.
+  This is just like the `Zeroable::zeroed` method, but as a `const fn`.
+
+## 1.15.0
+
+This primarily relaxes the bounds on a `From` impl.
+
+Previously:
+
+> `impl<T: NoUninit> From<Box<T>> for BoxBytes`
+
+Now:
+
+> `impl<T: ?Sized + sealed::BoxBytesOf> From<Box<T>> for BoxBytes`
+
+All related functions and methods are similarly updated.
+
+We believe this to be backwards compatible with all previous uses,
+and now `BoxBytes` can be converted to/from more types than before.
+
+## 1.14.3
+
+* The new std simd nightly features are apparently arch-specific.
+  This adjusts the feature activation to be x86/ x86_64 only.
+
+## 1.14.2
+
+* Changes the name of the Nightly feature activated by the crate's
+  `nightly_stdsimd` feature. This is needed as of (approximately) Nightly
+  2024-02-06 and later, because the Nightly feature was changed.
+
+## 1.14.1
+
+* docs clarifications.
+
+## 1.14
+
+* `write_zeroes` and `fill_zeroes` functions: Writes (to one) or fills (a slice)
+  zero bytes to all bytes covered by the provided reference. If your type has
+  padding, this will even zero out the padding bytes.
+* `align_offset` feature: causes pointer alignment checks to use the
+  `align_offset` pointer method rather than as-casting the pointer to `usize`.
+  This *may* improve codegen, if the compiler would have otherwise thought that
+  the pointer address escaped. No formal benchmarks have been done either way.
+* `must_cast` feature: Adds `must_*` family of functions. These functions will
+  fail to compile if the cast requested can't be statically known to succeed.
+  The error messages can be kinda bad when this happens, but eliminating the
+  possibility of a runtime error might be worth it to you.
+
+## 1.13.1
+
+* Remove the requirement for the *source* data type to be `AnyBitPattern` on
+  `pod_collect_to_vec`, allowing you to pod collect vecs of `char` into vecs of
+  `u32`, or whatever.
+
+## 1.13
+
+* Now depends on `bytemuck_derive-1.4.0`
+* Various small enhancements that would have been patch version updates, but
+  which have been rolled into this minor version update.
+
+## 1.12.4
+
+* This has additional impls for existing traits and cleans up some internal code,
+  but there's no new functions so I guess it counts as just a patch release.
+
+## 1.12.3
+
+* This bugfix makes the crate do stuff with `Arc` or not based on the
+  `target_has_atomic` config. Previously, some targets that have allocation but
+  not atomics were getting errors. This raises the MSRV of the
+  `extern_crate_alloc` feature to 1.60, but opt-in features are *not* considered
+  to be hard locked to 1.34 like the basic build of the crate is.
+
+## 1.12.2
+
+* Fixes `try_pod_read_unaligned` bug that made it always fail unless the target
+  type was exactly pointer sized in which case UB *could* happen. The
+  `CheckedBitPattern::is_valid_bit_pattern` was being asked to check that a
+  *reference* to the `pod` value was a valid bit pattern, rather than the actual
+  bit pattern itself, and so the check could in some cases be illegally
+  bypassed.
+
 ## 1.12.1
 
 * Patch bumped the required `bytemuck_derive` version because of a regression in
diff --git a/derive/Cargo.toml b/derive/Cargo.toml
index 2335922..619f11d 100644
--- a/derive/Cargo.toml
+++ b/derive/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "bytemuck_derive"
 description = "derive proc-macros for `bytemuck`"
-version = "1.2.1"
+version = "1.6.1"
 authors = ["Lokathor <zefria@gmail.com>"]
 repository = "https://github.com/Lokathor/bytemuck"
 readme = "README.md"
@@ -15,10 +15,10 @@ name = "bytemuck_derive"
 proc-macro = true
 
 [dependencies]
-# syn seems to have broken backwards compability in this version https://github.com/dtolnay/syn/issues/1194
-syn = "1.0.99"
+# syn seems to have broken backwards compatibility in this version https://github.com/dtolnay/syn/issues/1194
+syn = "2.0.1"
 quote = "1"
-proc-macro2 = "1"
+proc-macro2 = "1.0.60"
 
 [dev-dependencies]
 bytemuck = { path = "../", features = ["derive"] }
diff --git a/derive/README.md b/derive/README.md
index 553a40e..bb153e0 100644
--- a/derive/README.md
+++ b/derive/README.md
@@ -2,3 +2,9 @@
 # bytemuck_derive
 
 Derive macros for [bytemuck](https://docs.rs/bytemuck) traits.
+
+MSRV: None!
+
+This is an opt-in bonus feature for `bytemuck` that doesn't particularly do
+anything you couldn't do yourself, and so MSRV is not a strong consideration for
+this crate.
diff --git a/derive/changelog.md b/derive/changelog.md
index 12cee2b..1e9d990 100644
--- a/derive/changelog.md
+++ b/derive/changelog.md
@@ -1,9 +1,39 @@
 
 ## `bytemuck_derive` changelog
 
+## 1.6.0
+
+* This allows `CheckedBitPattern` to be derived for enums with fields.
+  The repr must be one of the following:
+  * `#[repr(C)]`
+  * `#[repr(C, int)]`
+  * `#[repr(int)]`
+  * `#[repr(transparent)]`
+
+## 1.5.0
+
+* The `Zeroable` derive now allows custom bounds. See the rustdoc for an explanation.
+
+## 1.4.1
+
+* Move the `syn` dependency to use version 2.
+  This should not affect the public API in any way.
+
+## 1.4.0
+
+* `ByteEq` and `ByteHash` derives will make `Eq` and `Hash` impls that treat the
+  value as a `&[u8]` during equality checks and hashing. This provides a large
+  codegen improvement for some types.
+* Derives of `repr(int)` enums should now accept byte literal values as the
+  discriminant.
+
+## 1.3.0
+
+* Allow `repr(transparent)` to be used generically in `derive(Pod)`.
+
 ## 1.2.1
 
-* Fixed a regression of the `align(N)` attribute that occured during otherwise
+* Fixed a regression of the `align(N)` attribute that occurred during otherwise
   routine cleanup.
 
 ## 1.2.0
@@ -13,7 +43,7 @@
   issuing this minor version bump in the `bytemuck_derive` crate. Because it's
   possible to *reduce* the minimum required version of a dep in only a patch
   release, I'm going to ratchet the required version of `syn` all the way up to
-  "curret" (1.0.99). If absolutely necessary we could probably reduce the
+  "current" (1.0.99). If absolutely necessary we could probably reduce the
   minimum `syn` version again in a patch release for 1.2, but I don't want to
   play this dance too much so I'd rather make each jump as big as can possibly
   be. [Issue 122](https://github.com/Lokathor/bytemuck/issues/122). **Note:**
diff --git a/derive/src/lib.rs b/derive/src/lib.rs
index 8a877f8..ee8f235 100644
--- a/derive/src/lib.rs
+++ b/derive/src/lib.rs
@@ -9,7 +9,8 @@ use quote::quote;
 use syn::{parse_macro_input, DeriveInput, Result};
 
 use crate::traits::{
-  AnyBitPattern, Contiguous, Derivable, CheckedBitPattern, NoUninit, Pod, TransparentWrapper, Zeroable,
+  bytemuck_crate_name, AnyBitPattern, CheckedBitPattern, Contiguous, Derivable,
+  NoUninit, Pod, TransparentWrapper, Zeroable,
 };
 
 /// Derive the `Pod` trait for a struct
@@ -22,21 +23,71 @@ use crate::traits::{
 /// - All fields in the struct must implement `Pod`
 /// - The struct must be `#[repr(C)]` or `#[repr(transparent)]`
 /// - The struct must not contain any padding bytes
-/// - The struct contains no generic parameters
+/// - The struct contains no generic parameters, if it is not
+///   `#[repr(transparent)]`
 ///
-/// ## Example
+/// ## Examples
 ///
 /// ```rust
+/// # use std::marker::PhantomData;
 /// # use bytemuck_derive::{Pod, Zeroable};
-///
 /// #[derive(Copy, Clone, Pod, Zeroable)]
 /// #[repr(C)]
 /// struct Test {
 ///   a: u16,
 ///   b: u16,
 /// }
+///
+/// #[derive(Copy, Clone, Pod, Zeroable)]
+/// #[repr(transparent)]
+/// struct Generic<A, B> {
+///   a: A,
+///   b: PhantomData<B>,
+/// }
+/// ```
+///
+/// If the struct is generic, it must be `#[repr(transparent)]` also.
+///
+/// ```compile_fail
+/// # use bytemuck::{Pod, Zeroable};
+/// # use std::marker::PhantomData;
+/// #[derive(Copy, Clone, Pod, Zeroable)]
+/// #[repr(C)] // must be `#[repr(transparent)]`
+/// struct Generic<A> {
+///   a: A,
+/// }
+/// ```
+///
+/// If the struct is generic and `#[repr(transparent)]`, then it is only `Pod`
+/// when all of its generics are `Pod`, not just its fields.
+///
+/// ```
+/// # use bytemuck::{Pod, Zeroable};
+/// # use std::marker::PhantomData;
+/// #[derive(Copy, Clone, Pod, Zeroable)]
+/// #[repr(transparent)]
+/// struct Generic<A, B> {
+///   a: A,
+///   b: PhantomData<B>,
+/// }
+///
+/// let _: u32 = bytemuck::cast(Generic { a: 4u32, b: PhantomData::<u32> });
 /// ```
-#[proc_macro_derive(Pod)]
+///
+/// ```compile_fail
+/// # use bytemuck::{Pod, Zeroable};
+/// # use std::marker::PhantomData;
+/// # #[derive(Copy, Clone, Pod, Zeroable)]
+/// # #[repr(transparent)]
+/// # struct Generic<A, B> {
+/// #   a: A,
+/// #   b: PhantomData<B>,
+/// # }
+/// struct NotPod;
+///
+/// let _: u32 = bytemuck::cast(Generic { a: 4u32, b: PhantomData::<NotPod> });
+/// ```
+#[proc_macro_derive(Pod, attributes(bytemuck))]
 pub fn derive_pod(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
   let expanded =
     derive_marker_trait::<Pod>(parse_macro_input!(input as DeriveInput));
@@ -52,12 +103,13 @@ pub fn derive_pod(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
 /// The following constraints need to be satisfied for the macro to succeed
 ///
 /// - All fields in the struct must to implement `AnyBitPattern`
-#[proc_macro_derive(AnyBitPattern)]
+#[proc_macro_derive(AnyBitPattern, attributes(bytemuck))]
 pub fn derive_anybitpattern(
   input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
-  let expanded =
-    derive_marker_trait::<AnyBitPattern>(parse_macro_input!(input as DeriveInput));
+  let expanded = derive_marker_trait::<AnyBitPattern>(parse_macro_input!(
+    input as DeriveInput
+  ));
 
   proc_macro::TokenStream::from(expanded)
 }
@@ -75,7 +127,6 @@ pub fn derive_anybitpattern(
 ///
 /// ```rust
 /// # use bytemuck_derive::{Zeroable};
-///
 /// #[derive(Copy, Clone, Zeroable)]
 /// #[repr(C)]
 /// struct Test {
@@ -83,7 +134,65 @@ pub fn derive_anybitpattern(
 ///   b: u16,
 /// }
 /// ```
-#[proc_macro_derive(Zeroable)]
+///
+/// # Custom bounds
+///
+/// Custom bounds for the derived `Zeroable` impl can be given using the
+/// `#[zeroable(bound = "")]` helper attribute.
+///
+/// Using this attribute additionally opts-in to "perfect derive" semantics,
+/// where instead of adding bounds for each generic type parameter, bounds are
+/// added for each field's type.
+///
+/// ## Examples
+///
+/// ```rust
+/// # use bytemuck::Zeroable;
+/// # use std::marker::PhantomData;
+/// #[derive(Clone, Zeroable)]
+/// #[zeroable(bound = "")]
+/// struct AlwaysZeroable<T> {
+///   a: PhantomData<T>,
+/// }
+///
+/// AlwaysZeroable::<std::num::NonZeroU8>::zeroed();
+/// ```
+///
+/// ```rust,compile_fail
+/// # use bytemuck::Zeroable;
+/// # use std::marker::PhantomData;
+/// #[derive(Clone, Zeroable)]
+/// #[zeroable(bound = "T: Copy")]
+/// struct ZeroableWhenTIsCopy<T> {
+///   a: PhantomData<T>,
+/// }
+///
+/// ZeroableWhenTIsCopy::<String>::zeroed();
+/// ```
+///
+/// The restriction that all fields must be Zeroable is still applied, and this
+/// is enforced using the mentioned "perfect derive" semantics.
+///
+/// ```rust
+/// # use bytemuck::Zeroable;
+/// #[derive(Clone, Zeroable)]
+/// #[zeroable(bound = "")]
+/// struct ZeroableWhenTIsZeroable<T> {
+///   a: T,
+/// }
+/// ZeroableWhenTIsZeroable::<u32>::zeroed();
+/// ```
+///
+/// ```rust,compile_fail
+/// # use bytemuck::Zeroable;
+/// # #[derive(Clone, Zeroable)]
+/// # #[zeroable(bound = "")]
+/// # struct ZeroableWhenTIsZeroable<T> {
+/// #   a: T,
+/// # }
+/// ZeroableWhenTIsZeroable::<String>::zeroed();
+/// ```
+#[proc_macro_derive(Zeroable, attributes(bytemuck, zeroable))]
 pub fn derive_zeroable(
   input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
@@ -99,8 +208,8 @@ pub fn derive_zeroable(
 /// for the `NoUninit` trait.
 ///
 /// The following constraints need to be satisfied for the macro to succeed
-/// (the rest of the constraints are guaranteed by the `NoUninit` subtrait bounds,
-/// i.e. the type must be `Sized + Copy + 'static`):
+/// (the rest of the constraints are guaranteed by the `NoUninit` subtrait
+/// bounds, i.e. the type must be `Sized + Copy + 'static`):
 ///
 /// If applied to a struct:
 /// - All fields in the struct must implement `NoUninit`
@@ -109,7 +218,7 @@ pub fn derive_zeroable(
 /// - The struct must contain no generic parameters
 ///
 /// If applied to an enum:
-/// - The enum must be explicit `#[repr(Int)]`
+/// - 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)]
@@ -128,22 +237,24 @@ pub fn derive_no_uninit(
 /// for the `CheckedBitPattern` trait and derives the required `Bits` type
 /// definition and `is_valid_bit_pattern` method for the type automatically.
 ///
-/// The following constraints need to be satisfied for the macro to succeed
-/// (the rest of the constraints are guaranteed by the `CheckedBitPattern` subtrait bounds,
-/// i.e. are guaranteed by the requirements of the `NoUninit` trait which `CheckedBitPattern`
-/// is a subtrait of):
+/// The following constraints need to be satisfied for the macro to succeed:
 ///
 /// If applied to a struct:
 /// - All fields must implement `CheckedBitPattern`
+/// - The struct must be `#[repr(C)]` or `#[repr(transparent)]`
+/// - The struct must contain no generic parameters
 ///
 /// If applied to an enum:
-/// - All requirements already checked by `NoUninit`, just impls the trait
+/// - The enum must be explicit `#[repr(Int)]`
+/// - All fields in variants must implement `CheckedBitPattern`
+/// - The enum must contain no generic parameters
 #[proc_macro_derive(CheckedBitPattern)]
 pub fn derive_maybe_pod(
   input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
-  let expanded =
-    derive_marker_trait::<CheckedBitPattern>(parse_macro_input!(input as DeriveInput));
+  let expanded = derive_marker_trait::<CheckedBitPattern>(parse_macro_input!(
+    input as DeriveInput
+  ));
 
   proc_macro::TokenStream::from(expanded)
 }
@@ -157,26 +268,58 @@ pub fn derive_maybe_pod(
 ///
 /// - The struct must be `#[repr(transparent)]`
 /// - The struct must contain the `Wrapped` type
+/// - Any ZST fields must be [`Zeroable`][derive@Zeroable].
 ///
 /// If the struct only contains a single field, the `Wrapped` type will
-/// automatically be determined if there is more then one field in the struct,
+/// automatically be determined. If there is more then one field in the struct,
 /// you need to specify the `Wrapped` type using `#[transparent(T)]`
 ///
-/// ## Example
+/// ## Examples
 ///
 /// ```rust
 /// # use bytemuck_derive::TransparentWrapper;
 /// # use std::marker::PhantomData;
+/// #[derive(Copy, Clone, TransparentWrapper)]
+/// #[repr(transparent)]
+/// #[transparent(u16)]
+/// struct Test<T> {
+///   inner: u16,
+///   extra: PhantomData<T>,
+/// }
+/// ```
+///
+/// If the struct contains more than one field, the `Wrapped` type must be
+/// explicitly specified.
 ///
+/// ```rust,compile_fail
+/// # use bytemuck_derive::TransparentWrapper;
+/// # use std::marker::PhantomData;
 /// #[derive(Copy, Clone, TransparentWrapper)]
 /// #[repr(transparent)]
+/// // missing `#[transparent(u16)]`
+/// struct Test<T> {
+///   inner: u16,
+///   extra: PhantomData<T>,
+/// }
+/// ```
+///
+/// Any ZST fields must be `Zeroable`.
+///
+/// ```rust,compile_fail
+/// # use bytemuck_derive::TransparentWrapper;
+/// # use std::marker::PhantomData;
+/// struct NonTransparentSafeZST;
+///
+/// #[derive(TransparentWrapper)]
+/// #[repr(transparent)]
 /// #[transparent(u16)]
 /// struct Test<T> {
 ///   inner: u16,
 ///   extra: PhantomData<T>,
+///   another_extra: NonTransparentSafeZST, // not `Zeroable`
 /// }
 /// ```
-#[proc_macro_derive(TransparentWrapper, attributes(transparent))]
+#[proc_macro_derive(TransparentWrapper, attributes(bytemuck, transparent))]
 pub fn derive_transparent(
   input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
@@ -221,41 +364,269 @@ pub fn derive_contiguous(
   proc_macro::TokenStream::from(expanded)
 }
 
+/// Derive the `PartialEq` and `Eq` trait for a type
+///
+/// The macro implements `PartialEq` and `Eq` by casting both sides of the
+/// comparison to a byte slice and then compares those.
+///
+/// ## Warning
+///
+/// Since this implements a byte wise comparison, the behavior of floating point
+/// numbers does not match their usual comparison behavior. Additionally other
+/// custom comparison behaviors of the individual fields are also ignored. This
+/// also does not implement `StructuralPartialEq` / `StructuralEq` like
+/// `PartialEq` / `Eq` would. This means you can't pattern match on the values.
+///
+/// ## Example
+///
+/// ```rust
+/// # use bytemuck_derive::{ByteEq, NoUninit};
+/// #[derive(Copy, Clone, NoUninit, ByteEq)]
+/// #[repr(C)]
+/// struct Test {
+///   a: u32,
+///   b: char,
+///   c: f32,
+/// }
+/// ```
+#[proc_macro_derive(ByteEq)]
+pub fn derive_byte_eq(
+  input: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+  let input = parse_macro_input!(input as DeriveInput);
+  let crate_name = bytemuck_crate_name(&input);
+  let ident = input.ident;
+
+  proc_macro::TokenStream::from(quote! {
+    impl ::core::cmp::PartialEq for #ident {
+      #[inline]
+      #[must_use]
+      fn eq(&self, other: &Self) -> bool {
+        #crate_name::bytes_of(self) == #crate_name::bytes_of(other)
+      }
+    }
+    impl ::core::cmp::Eq for #ident { }
+  })
+}
+
+/// Derive the `Hash` trait for a type
+///
+/// The macro implements `Hash` by casting the value to a byte slice and hashing
+/// that.
+///
+/// ## Warning
+///
+/// The hash does not match the standard library's `Hash` derive.
+///
+/// ## Example
+///
+/// ```rust
+/// # use bytemuck_derive::{ByteHash, NoUninit};
+/// #[derive(Copy, Clone, NoUninit, ByteHash)]
+/// #[repr(C)]
+/// struct Test {
+///   a: u32,
+///   b: char,
+///   c: f32,
+/// }
+/// ```
+#[proc_macro_derive(ByteHash)]
+pub fn derive_byte_hash(
+  input: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+  let input = parse_macro_input!(input as DeriveInput);
+  let crate_name = bytemuck_crate_name(&input);
+  let ident = input.ident;
+
+  proc_macro::TokenStream::from(quote! {
+    impl ::core::hash::Hash for #ident {
+      #[inline]
+      fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
+        ::core::hash::Hash::hash_slice(#crate_name::bytes_of(self), state)
+      }
+
+      #[inline]
+      fn hash_slice<H: ::core::hash::Hasher>(data: &[Self], state: &mut H) {
+        ::core::hash::Hash::hash_slice(#crate_name::cast_slice::<_, u8>(data), state)
+      }
+    }
+  })
+}
+
 /// Basic wrapper for error handling
 fn derive_marker_trait<Trait: Derivable>(input: DeriveInput) -> TokenStream {
   derive_marker_trait_inner::<Trait>(input)
     .unwrap_or_else(|err| err.into_compile_error())
 }
 
+/// Find `#[name(key = "value")]` helper attributes on the struct, and return
+/// their `"value"`s parsed with `parser`.
+///
+/// Returns an error if any attributes with the given `name` do not match the
+/// expected format. Returns `Ok([])` if no attributes with `name` are found.
+fn find_and_parse_helper_attributes<P: syn::parse::Parser + Copy>(
+  attributes: &[syn::Attribute], name: &str, key: &str, parser: P,
+  example_value: &str, invalid_value_msg: &str,
+) -> Result<Vec<P::Output>> {
+  let invalid_format_msg =
+    format!("{name} attribute must be `{name}({key} = \"{example_value}\")`",);
+  let values_to_check = attributes.iter().filter_map(|attr| match &attr.meta {
+    // If a `Path` matches our `name`, return an error, else ignore it.
+    // e.g. `#[zeroable]`
+    syn::Meta::Path(path) => path
+      .is_ident(name)
+      .then(|| Err(syn::Error::new_spanned(path, &invalid_format_msg))),
+    // If a `NameValue` matches our `name`, return an error, else ignore it.
+    // e.g. `#[zeroable = "hello"]`
+    syn::Meta::NameValue(namevalue) => {
+      namevalue.path.is_ident(name).then(|| {
+        Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg))
+      })
+    }
+    // If a `List` matches our `name`, match its contents to our format, else
+    // ignore it. If its contents match our format, return the value, else
+    // return an error.
+    syn::Meta::List(list) => list.path.is_ident(name).then(|| {
+      let namevalue: syn::MetaNameValue = syn::parse2(list.tokens.clone())
+        .map_err(|_| {
+          syn::Error::new_spanned(&list.tokens, &invalid_format_msg)
+        })?;
+      if namevalue.path.is_ident(key) {
+        match namevalue.value {
+          syn::Expr::Lit(syn::ExprLit {
+            lit: syn::Lit::Str(strlit), ..
+          }) => Ok(strlit),
+          _ => {
+            Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg))
+          }
+        }
+      } else {
+        Err(syn::Error::new_spanned(&namevalue.path, &invalid_format_msg))
+      }
+    }),
+  });
+  // Parse each value found with the given parser, and return them if no errors
+  // occur.
+  values_to_check
+    .map(|lit| {
+      let lit = lit?;
+      lit.parse_with(parser).map_err(|err| {
+        syn::Error::new_spanned(&lit, format!("{invalid_value_msg}: {err}"))
+      })
+    })
+    .collect()
+}
+
 fn derive_marker_trait_inner<Trait: Derivable>(
-  input: DeriveInput,
+  mut input: DeriveInput,
 ) -> Result<TokenStream> {
+  let crate_name = bytemuck_crate_name(&input);
+  let trait_ = Trait::ident(&input, &crate_name)?;
+  // If this trait allows explicit bounds, and any explicit bounds were given,
+  // then use those explicit bounds. Else, apply the default bounds (bound
+  // each generic type on this trait).
+  if let Some(name) = Trait::explicit_bounds_attribute_name() {
+    // See if any explicit bounds were given in attributes.
+    let explicit_bounds = find_and_parse_helper_attributes(
+      &input.attrs,
+      name,
+      "bound",
+      <syn::punctuated::Punctuated<syn::WherePredicate, syn::Token![,]>>::parse_terminated,
+      "Type: Trait",
+      "invalid where predicate",
+    )?;
+
+    if !explicit_bounds.is_empty() {
+      // Explicit bounds were given.
+      // Enforce explicitly given bounds, and emit "perfect derive" (i.e. add
+      // bounds for each field's type).
+      let explicit_bounds = explicit_bounds
+        .into_iter()
+        .flatten()
+        .collect::<Vec<syn::WherePredicate>>();
+
+      let predicates = &mut input.generics.make_where_clause().predicates;
+
+      predicates.extend(explicit_bounds);
+
+      let fields = match &input.data {
+        syn::Data::Struct(syn::DataStruct { fields, .. }) => fields.clone(),
+        syn::Data::Union(_) => {
+          return Err(syn::Error::new_spanned(
+            trait_,
+            &"perfect derive is not supported for unions",
+          ));
+        }
+        syn::Data::Enum(_) => {
+          return Err(syn::Error::new_spanned(
+            trait_,
+            &"perfect derive is not supported for enums",
+          ));
+        }
+      };
+
+      for field in fields {
+        let ty = field.ty;
+        predicates.push(syn::parse_quote!(
+          #ty: #trait_
+        ));
+      }
+    } else {
+      // No explicit bounds were given.
+      // Enforce trait bound on all type generics.
+      add_trait_marker(&mut input.generics, &trait_);
+    }
+  } else {
+    // This trait does not allow explicit bounds.
+    // Enforce trait bound on all type generics.
+    add_trait_marker(&mut input.generics, &trait_);
+  }
+
   let name = &input.ident;
 
   let (impl_generics, ty_generics, where_clause) =
     input.generics.split_for_impl();
 
-  let trait_ = Trait::ident();
   Trait::check_attributes(&input.data, &input.attrs)?;
-  let asserts = Trait::asserts(&input)?;
-  let trait_params = Trait::generic_params(&input)?;
-  let (trait_impl_extras, trait_impl) = Trait::trait_impl(&input)?;
+  let asserts = Trait::asserts(&input, &crate_name)?;
+  let (trait_impl_extras, trait_impl) = Trait::trait_impl(&input, &crate_name)?;
 
-  let implies_trait = if let Some(implies_trait) = Trait::implies_trait() {
-    quote!(unsafe impl #implies_trait for #name {})
+  let implies_trait = if let Some(implies_trait) =
+    Trait::implies_trait(&crate_name)
+  {
+    quote!(unsafe impl #impl_generics #implies_trait for #name #ty_generics #where_clause {})
   } else {
     quote!()
   };
 
+  let where_clause =
+    if Trait::requires_where_clause() { where_clause } else { None };
+
   Ok(quote! {
     #asserts
 
     #trait_impl_extras
 
-    unsafe impl #impl_generics #trait_ #trait_params for #name #ty_generics #where_clause {
+    unsafe impl #impl_generics #trait_ for #name #ty_generics #where_clause {
       #trait_impl
     }
 
     #implies_trait
   })
 }
+
+/// Add a trait marker to the generics if it is not already present
+fn add_trait_marker(generics: &mut syn::Generics, trait_name: &syn::Path) {
+  // Get each generic type parameter.
+  let type_params = generics
+    .type_params()
+    .map(|param| &param.ident)
+    .map(|param| {
+      syn::parse_quote!(
+        #param: #trait_name
+      )
+    })
+    .collect::<Vec<syn::WherePredicate>>();
+
+  generics.make_where_clause().predicates.extend(type_params);
+}
diff --git a/derive/src/traits.rs b/derive/src/traits.rs
index 815e77c..abe27f1 100644
--- a/derive/src/traits.rs
+++ b/derive/src/traits.rs
@@ -1,57 +1,59 @@
 #![allow(unused_imports)]
+use std::{cmp, convert::TryFrom};
+
 use proc_macro2::{Ident, Span, TokenStream, TokenTree};
 use quote::{quote, quote_spanned, ToTokens};
-use syn::{*,
-  parse::{Parse, Parser, ParseStream},
+use syn::{
+  parse::{Parse, ParseStream, Parser},
   punctuated::Punctuated,
   spanned::Spanned,
-  Result,
+  Result, *,
 };
 
 macro_rules! bail {
-  ($msg:expr $(,)?) => (
+  ($msg:expr $(,)?) => {
     return Err(Error::new(Span::call_site(), &$msg[..]))
-  );
+  };
 
-  ( $msg:expr => $span_to_blame:expr $(,)? ) => (
+  ( $msg:expr => $span_to_blame:expr $(,)? ) => {
     return Err(Error::new_spanned(&$span_to_blame, $msg))
-  );
+  };
 }
 
 pub trait Derivable {
-  fn ident() -> TokenStream;
-  fn implies_trait() -> Option<TokenStream> {
+  fn ident(input: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path>;
+  fn implies_trait(_crate_name: &TokenStream) -> Option<TokenStream> {
     None
   }
-  fn generic_params(_input: &DeriveInput) -> Result<TokenStream> {
-    Ok(quote!())
-  }
-  fn asserts(_input: &DeriveInput) -> Result<TokenStream> {
+  fn asserts(_input: &DeriveInput, _crate_name: &TokenStream) -> Result<TokenStream> {
     Ok(quote!())
   }
-  fn check_attributes(
-    _ty: &Data, _attributes: &[Attribute],
-  ) -> Result<()> {
+  fn check_attributes(_ty: &Data, _attributes: &[Attribute]) -> Result<()> {
     Ok(())
   }
-  fn trait_impl(
-    _input: &DeriveInput,
-  ) -> Result<(TokenStream, TokenStream)> {
+  fn trait_impl(_input: &DeriveInput, _crate_name: &TokenStream) -> Result<(TokenStream, TokenStream)> {
     Ok((quote!(), quote!()))
   }
+  fn requires_where_clause() -> bool {
+    true
+  }
+  fn explicit_bounds_attribute_name() -> Option<&'static str> {
+    None
+  }
 }
 
 pub struct Pod;
 
 impl Derivable for Pod {
-  fn ident() -> TokenStream {
-    quote!(::bytemuck::Pod)
+  fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> {
+    Ok(syn::parse_quote!(#crate_name::Pod))
   }
 
-  fn asserts(input: &DeriveInput) -> Result<TokenStream> {
+  fn asserts(input: &DeriveInput, crate_name: &TokenStream) -> Result<TokenStream> {
     let repr = get_repr(&input.attrs)?;
 
-    let completly_packed = repr.packed == Some(1);
+    let completly_packed =
+      repr.packed == Some(1) || repr.repr == Repr::Transparent;
 
     if !completly_packed && !input.generics.params.is_empty() {
       bail!("\
@@ -69,7 +71,7 @@ impl Derivable for Pod {
           None
         };
         let assert_fields_are_pod =
-          generate_fields_are_trait(input, Self::ident())?;
+          generate_fields_are_trait(input, Self::ident(input, crate_name)?)?;
 
         Ok(quote!(
           #assert_no_padding
@@ -81,9 +83,7 @@ impl Derivable for Pod {
     }
   }
 
-  fn check_attributes(
-    _ty: &Data, attributes: &[Attribute],
-  ) -> Result<()> {
+  fn check_attributes(_ty: &Data, attributes: &[Attribute]) -> Result<()> {
     let repr = get_repr(attributes)?;
     match repr.repr {
       Repr::C => Ok(()),
@@ -98,19 +98,21 @@ impl Derivable for Pod {
 pub struct AnyBitPattern;
 
 impl Derivable for AnyBitPattern {
-  fn ident() -> TokenStream {
-    quote!(::bytemuck::AnyBitPattern)
+  fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> {
+    Ok(syn::parse_quote!(#crate_name::AnyBitPattern))
   }
 
-  fn implies_trait() -> Option<TokenStream> {
-    Some(quote!(::bytemuck::Zeroable))
+  fn implies_trait(crate_name: &TokenStream) -> Option<TokenStream> {
+    Some(quote!(#crate_name::Zeroable))
   }
 
-  fn asserts(input: &DeriveInput) -> Result<TokenStream> {
+  fn asserts(input: &DeriveInput, crate_name: &TokenStream) -> Result<TokenStream> {
     match &input.data {
       Data::Union(_) => Ok(quote!()), // unions are always `AnyBitPattern`
-      Data::Struct(_) => generate_fields_are_trait(input, Self::ident()),
-      Data::Enum(_) => bail!("Deriving AnyBitPattern is not supported for enums"),
+      Data::Struct(_) => generate_fields_are_trait(input, Self::ident(input, crate_name)?),
+      Data::Enum(_) => {
+        bail!("Deriving AnyBitPattern is not supported for enums")
+      }
     }
   }
 }
@@ -118,29 +120,31 @@ impl Derivable for AnyBitPattern {
 pub struct Zeroable;
 
 impl Derivable for Zeroable {
-  fn ident() -> TokenStream {
-    quote!(::bytemuck::Zeroable)
+  fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> {
+    Ok(syn::parse_quote!(#crate_name::Zeroable))
   }
 
-  fn asserts(input: &DeriveInput) -> Result<TokenStream> {
+  fn asserts(input: &DeriveInput, crate_name: &TokenStream) -> Result<TokenStream> {
     match &input.data {
       Data::Union(_) => Ok(quote!()), // unions are always `Zeroable`
-      Data::Struct(_) => generate_fields_are_trait(input, Self::ident()),
+      Data::Struct(_) => generate_fields_are_trait(input, Self::ident(input, crate_name)?),
       Data::Enum(_) => bail!("Deriving Zeroable is not supported for enums"),
     }
   }
+
+  fn explicit_bounds_attribute_name() -> Option<&'static str> {
+    Some("zeroable")
+  }
 }
 
 pub struct NoUninit;
 
 impl Derivable for NoUninit {
-  fn ident() -> TokenStream {
-    quote!(::bytemuck::NoUninit)
+  fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> {
+    Ok(syn::parse_quote!(#crate_name::NoUninit))
   }
 
-  fn check_attributes(
-    ty: &Data, attributes: &[Attribute],
-  ) -> Result<()> {
+  fn check_attributes(ty: &Data, attributes: &[Attribute]) -> Result<()> {
     let repr = get_repr(attributes)?;
     match ty {
       Data::Struct(_) => match repr.repr {
@@ -156,7 +160,7 @@ impl Derivable for NoUninit {
     }
   }
 
-  fn asserts(input: &DeriveInput) -> Result<TokenStream> {
+  fn asserts(input: &DeriveInput, crate_name: &TokenStream) -> Result<TokenStream> {
     if !input.generics.params.is_empty() {
       bail!("NoUninit cannot be derived for structs containing generic parameters because the padding requirements can't be verified for generic structs");
     }
@@ -165,7 +169,7 @@ impl Derivable for NoUninit {
       Data::Struct(DataStruct { .. }) => {
         let assert_no_padding = generate_assert_no_padding(&input)?;
         let assert_fields_are_no_padding =
-          generate_fields_are_trait(&input, Self::ident())?;
+          generate_fields_are_trait(&input, Self::ident(input, crate_name)?)?;
 
         Ok(quote!(
             #assert_no_padding
@@ -179,13 +183,11 @@ impl Derivable for NoUninit {
           Ok(quote!())
         }
       }
-      Data::Union(_) => bail!("NoUninit cannot be derived for unions"), // shouldn't be possible since we already error in attribute check for this case
+      Data::Union(_) => bail!("NoUninit cannot be derived for unions"), /* shouldn't be possible since we already error in attribute check for this case */
     }
   }
 
-  fn trait_impl(
-    _input: &DeriveInput,
-  ) -> Result<(TokenStream, TokenStream)> {
+  fn trait_impl(_input: &DeriveInput, _crate_name: &TokenStream) -> Result<(TokenStream, TokenStream)> {
     Ok((quote!(), quote!()))
   }
 }
@@ -193,29 +195,35 @@ impl Derivable for NoUninit {
 pub struct CheckedBitPattern;
 
 impl Derivable for CheckedBitPattern {
-  fn ident() -> TokenStream {
-    quote!(::bytemuck::CheckedBitPattern)
+  fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> {
+    Ok(syn::parse_quote!(#crate_name::CheckedBitPattern))
   }
 
-  fn check_attributes(
-    ty: &Data, attributes: &[Attribute],
-  ) -> Result<()> {
+  fn check_attributes(ty: &Data, attributes: &[Attribute]) -> Result<()> {
     let repr = get_repr(attributes)?;
     match ty {
       Data::Struct(_) => match repr.repr {
         Repr::C | Repr::Transparent => Ok(()),
         _ => bail!("CheckedBitPattern derive requires the struct to be #[repr(C)] or #[repr(transparent)]"),
       },
-      Data::Enum(_) => if repr.repr.is_integer() {
-        Ok(())
-      } else {
-        bail!("CheckedBitPattern requires the enum to be an explicit #[repr(Int)]")
-      },
+      Data::Enum(DataEnum { variants,.. }) => {
+        if !enum_has_fields(variants.iter()){
+          if repr.repr.is_integer() {
+            Ok(())
+          } else {
+            bail!("CheckedBitPattern requires the enum to be an explicit #[repr(Int)]")
+          }
+        } else if matches!(repr.repr, Repr::Rust) {
+          bail!("CheckedBitPattern requires an explicit repr annotation because `repr(Rust)` doesn't have a specified type layout")
+        } else {
+          Ok(())
+        }
+      }
       Data::Union(_) => bail!("CheckedBitPattern can only be derived on enums and structs")
     }
   }
 
-  fn asserts(input: &DeriveInput) -> Result<TokenStream> {
+  fn asserts(input: &DeriveInput, crate_name: &TokenStream) -> Result<TokenStream> {
     if !input.generics.params.is_empty() {
       bail!("CheckedBitPattern cannot be derived for structs containing generic parameters");
     }
@@ -223,24 +231,24 @@ impl Derivable for CheckedBitPattern {
     match &input.data {
       Data::Struct(DataStruct { .. }) => {
         let assert_fields_are_maybe_pod =
-          generate_fields_are_trait(&input, Self::ident())?;
+          generate_fields_are_trait(&input, Self::ident(input, crate_name)?)?;
 
         Ok(assert_fields_are_maybe_pod)
       }
-      Data::Enum(_) => Ok(quote!()), // nothing needed, already guaranteed OK by NoUninit
-      Data::Union(_) => bail!("Internal error in CheckedBitPattern derive"), // shouldn't be possible since we already error in attribute check for this case
+      Data::Enum(_) => Ok(quote!()), /* nothing needed, already guaranteed OK by NoUninit */
+      Data::Union(_) => bail!("Internal error in CheckedBitPattern derive"), /* shouldn't be possible since we already error in attribute check for this case */
     }
   }
 
-  fn trait_impl(
-    input: &DeriveInput,
-  ) -> Result<(TokenStream, TokenStream)> {
+  fn trait_impl(input: &DeriveInput, crate_name: &TokenStream) -> Result<(TokenStream, TokenStream)> {
     match &input.data {
       Data::Struct(DataStruct { fields, .. }) => {
-        generate_checked_bit_pattern_struct(&input.ident, fields, &input.attrs)
-      },
-      Data::Enum(_) => generate_checked_bit_pattern_enum(input),
-      Data::Union(_) => bail!("Internal error in CheckedBitPattern derive"), // shouldn't be possible since we already error in attribute check for this case
+        generate_checked_bit_pattern_struct(&input.ident, fields, &input.attrs, crate_name)
+      }
+      Data::Enum(DataEnum { variants, .. }) => {
+        generate_checked_bit_pattern_enum(input, variants, crate_name)
+      }
+      Data::Union(_) => bail!("Internal error in CheckedBitPattern derive"), /* shouldn't be possible since we already error in attribute check for this case */
     }
   }
 }
@@ -266,74 +274,107 @@ impl TransparentWrapper {
 }
 
 impl Derivable for TransparentWrapper {
-  fn ident() -> TokenStream {
-    quote!(::bytemuck::TransparentWrapper)
-  }
-
-  fn generic_params(input: &DeriveInput) -> Result<TokenStream> {
+  fn ident(input: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> {
     let fields = get_struct_fields(input)?;
 
-    match Self::get_wrapper_type(&input.attrs, &fields) {
-      | Some(ty) => Ok(quote!(<#ty>)),
-      | None => bail!("\
+    let ty = match Self::get_wrapper_type(&input.attrs, &fields) {
+      Some(ty) => ty,
+      None => bail!(
+        "\
         when deriving TransparentWrapper for a struct with more than one field \
         you need to specify the transparent field using #[transparent(T)]\
-      "),
-    }
+      "
+      ),
+    };
+
+    Ok(syn::parse_quote!(#crate_name::TransparentWrapper<#ty>))
   }
 
-  fn asserts(input: &DeriveInput) -> Result<TokenStream> {
+  fn asserts(input: &DeriveInput, crate_name: &TokenStream) -> Result<TokenStream> {
+    let (impl_generics, _ty_generics, where_clause) =
+      input.generics.split_for_impl();
     let fields = get_struct_fields(input)?;
     let wrapped_type = match Self::get_wrapper_type(&input.attrs, &fields) {
       Some(wrapped_type) => wrapped_type.to_string(),
       None => unreachable!(), /* other code will already reject this derive */
     };
-    let mut wrapped_fields = fields
-      .iter()
-      .filter(|field| field.ty.to_token_stream().to_string() == wrapped_type);
-    if let None = wrapped_fields.next() {
-      bail!("TransparentWrapper must have one field of the wrapped type");
-    };
-    if let Some(_) = wrapped_fields.next() {
-      bail!("TransparentWrapper can only have one field of the wrapped type")
+    let mut wrapped_field_ty = None;
+    let mut nonwrapped_field_tys = vec![];
+    for field in fields.iter() {
+      let field_ty = &field.ty;
+      if field_ty.to_token_stream().to_string() == wrapped_type {
+        if wrapped_field_ty.is_some() {
+          bail!(
+            "TransparentWrapper can only have one field of the wrapped type"
+          );
+        }
+        wrapped_field_ty = Some(field_ty);
+      } else {
+        nonwrapped_field_tys.push(field_ty);
+      }
+    }
+    if let Some(wrapped_field_ty) = wrapped_field_ty {
+      Ok(quote!(
+        const _: () = {
+          #[repr(transparent)]
+          #[allow(clippy::multiple_bound_locations)]
+          struct AssertWrappedIsWrapped #impl_generics((u8, ::core::marker::PhantomData<#wrapped_field_ty>), #(#nonwrapped_field_tys),*) #where_clause;
+          fn assert_zeroable<Z: #crate_name::Zeroable>() {}
+          #[allow(clippy::multiple_bound_locations)]
+          fn check #impl_generics () #where_clause {
+            #(
+              assert_zeroable::<#nonwrapped_field_tys>();
+            )*
+          }
+        };
+      ))
     } else {
-      Ok(quote!())
+      bail!("TransparentWrapper must have one field of the wrapped type")
     }
   }
 
-  fn check_attributes(
-    _ty: &Data, attributes: &[Attribute],
-  ) -> Result<()> {
+  fn check_attributes(_ty: &Data, attributes: &[Attribute]) -> Result<()> {
     let repr = get_repr(attributes)?;
 
     match repr.repr {
       Repr::Transparent => Ok(()),
       _ => {
-        bail!("TransparentWrapper requires the struct to be #[repr(transparent)]")
+        bail!(
+          "TransparentWrapper requires the struct to be #[repr(transparent)]"
+        )
       }
     }
   }
+
+  fn requires_where_clause() -> bool {
+    false
+  }
 }
 
 pub struct Contiguous;
 
 impl Derivable for Contiguous {
-  fn ident() -> TokenStream {
-    quote!(::bytemuck::Contiguous)
+  fn ident(_: &DeriveInput, crate_name: &TokenStream) -> Result<syn::Path> {
+    Ok(syn::parse_quote!(#crate_name::Contiguous))
   }
 
-  fn trait_impl(
-    input: &DeriveInput,
-  ) -> Result<(TokenStream, TokenStream)> {
+  fn trait_impl(input: &DeriveInput, _crate_name: &TokenStream) -> Result<(TokenStream, TokenStream)> {
     let repr = get_repr(&input.attrs)?;
 
-    let integer_ty = if let Some(integer_ty) = repr.repr.as_integer_type() {
+    let integer_ty = if let Some(integer_ty) = repr.repr.as_integer() {
       integer_ty
     } else {
       bail!("Contiguous requires the enum to be #[repr(Int)]");
     };
 
     let variants = get_enum_variants(input)?;
+    if enum_has_fields(variants.clone()) {
+      return Err(Error::new_spanned(
+        &input,
+        "Only fieldless enums are supported",
+      ));
+    }
+
     let mut variants_with_discriminator =
       VariantDiscriminantIterator::new(variants);
 
@@ -358,12 +399,35 @@ impl Derivable for Contiguous {
     let min_lit = LitInt::new(&format!("{}", min), input.span());
     let max_lit = LitInt::new(&format!("{}", max), input.span());
 
+    // `from_integer` and `into_integer` are usually provided by the trait's default implementation.
+    // We override this implementation because it goes through `transmute_copy`, which can lead to
+    // inefficient assembly as seen in https://github.com/Lokathor/bytemuck/issues/175 .
+
     Ok((
       quote!(),
       quote! {
           type Int = #integer_ty;
+
+          #![allow(clippy::missing_docs_in_private_items)]
           const MIN_VALUE: #integer_ty = #min_lit;
+
+          #![allow(clippy::missing_docs_in_private_items)]
           const MAX_VALUE: #integer_ty = #max_lit;
+
+          #[inline]
+          fn from_integer(value: Self::Int) -> Option<Self> {
+            #[allow(clippy::manual_range_contains)]
+            if Self::MIN_VALUE <= value && value <= Self::MAX_VALUE {
+              Some(unsafe { ::core::mem::transmute(value) })
+            } else {
+              None
+            }
+          }
+
+          #[inline]
+          fn into_integer(self) -> Self::Int {
+              self as #integer_ty
+          }
       },
     ))
   }
@@ -387,7 +451,7 @@ fn get_fields(input: &DeriveInput) -> Result<Fields> {
 
 fn get_enum_variants<'a>(
   input: &'a DeriveInput,
-) -> Result<impl Iterator<Item = &'a Variant> + 'a> {
+) -> Result<impl Iterator<Item = &'a Variant> + Clone + 'a> {
   if let Data::Enum(DataEnum { variants, .. }) = &input.data {
     Ok(variants.iter())
   } else {
@@ -402,7 +466,7 @@ fn get_field_types<'a>(
 }
 
 fn generate_checked_bit_pattern_struct(
-  input_ident: &Ident, fields: &Fields, attrs: &[Attribute],
+  input_ident: &Ident, fields: &Fields, attrs: &[Attribute], crate_name: &TokenStream
 ) -> Result<(TokenStream, TokenStream)> {
   let bits_ty = Ident::new(&format!("{}Bits", input_ident), input_ident.span());
 
@@ -422,41 +486,58 @@ fn generate_checked_bit_pattern_struct(
   let field_name = &field_names[..];
   let field_ty = &field_tys[..];
 
-  let derive_dbg = quote!(#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]);
+  let derive_dbg =
+    quote!(#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]);
 
   Ok((
     quote! {
+        #[doc = #GENERATED_TYPE_DOCUMENTATION]
         #repr
-        #[derive(Clone, Copy, ::bytemuck::AnyBitPattern)]
+        #[derive(Clone, Copy, #crate_name::AnyBitPattern)]
         #derive_dbg
+        #[allow(missing_docs)]
         pub struct #bits_ty {
-            #(#field_name: <#field_ty as ::bytemuck::CheckedBitPattern>::Bits,)*
+            #(#field_name: <#field_ty as #crate_name::CheckedBitPattern>::Bits,)*
         }
     },
     quote! {
         type Bits = #bits_ty;
 
         #[inline]
-        #[allow(clippy::double_comparisons)]
+        #[allow(clippy::double_comparisons, unused)]
         fn is_valid_bit_pattern(bits: &#bits_ty) -> bool {
-            #(<#field_ty as ::bytemuck::CheckedBitPattern>::is_valid_bit_pattern(&bits.#field_name) && )* true
+            #(<#field_ty as #crate_name::CheckedBitPattern>::is_valid_bit_pattern(&{ bits.#field_name }) && )* true
         }
     },
   ))
 }
 
 fn generate_checked_bit_pattern_enum(
-  input: &DeriveInput,
+  input: &DeriveInput, variants: &Punctuated<Variant, Token![,]>, crate_name: &TokenStream
+) -> Result<(TokenStream, TokenStream)> {
+  if enum_has_fields(variants.iter()) {
+    generate_checked_bit_pattern_enum_with_fields(input, variants, crate_name)
+  } else {
+    generate_checked_bit_pattern_enum_without_fields(input, variants)
+  }
+}
+
+fn generate_checked_bit_pattern_enum_without_fields(
+  input: &DeriveInput, variants: &Punctuated<Variant, Token![,]>,
 ) -> Result<(TokenStream, TokenStream)> {
   let span = input.span();
   let mut variants_with_discriminant =
-    VariantDiscriminantIterator::new(get_enum_variants(input)?);
+    VariantDiscriminantIterator::new(variants.iter());
 
   let (min, max, count) = variants_with_discriminant.try_fold(
     (i64::max_value(), i64::min_value(), 0),
     |(min, max, count), res| {
       let discriminant = res?;
-      Ok::<_, Error>((i64::min(min, discriminant), i64::max(max, discriminant), count + 1))
+      Ok::<_, Error>((
+        i64::min(min, discriminant),
+        i64::max(max, discriminant),
+        count + 1,
+      ))
     },
   )?;
 
@@ -470,13 +551,12 @@ fn generate_checked_bit_pattern_enum(
     quote!(*bits >= #min_lit && *bits <= #max_lit)
   } else {
     // not contiguous range, check for each
-    let variant_lits =
-      VariantDiscriminantIterator::new(get_enum_variants(input)?)
-        .map(|res| {
-          let variant = res?;
-          Ok(LitInt::new(&format!("{}", variant), span))
-        })
-        .collect::<Result<Vec<_>>>()?;
+    let variant_lits = VariantDiscriminantIterator::new(variants.iter())
+      .map(|res| {
+        let variant = res?;
+        Ok(LitInt::new(&format!("{}", variant), span))
+      })
+      .collect::<Result<Vec<_>>>()?;
 
     // count is at least 1
     let first = &variant_lits[0];
@@ -486,11 +566,11 @@ fn generate_checked_bit_pattern_enum(
   };
 
   let repr = get_repr(&input.attrs)?;
-  let integer_ty = repr.repr.as_integer_type().unwrap(); // should be checked in attr check already
+  let integer = repr.repr.as_integer().unwrap(); // should be checked in attr check already
   Ok((
     quote!(),
     quote! {
-        type Bits = #integer_ty;
+        type Bits = #integer;
 
         #[inline]
         #[allow(clippy::double_comparisons)]
@@ -501,11 +581,250 @@ fn generate_checked_bit_pattern_enum(
   ))
 }
 
+fn generate_checked_bit_pattern_enum_with_fields(
+  input: &DeriveInput, variants: &Punctuated<Variant, Token![,]>, crate_name: &TokenStream
+) -> Result<(TokenStream, TokenStream)> {
+  let representation = get_repr(&input.attrs)?;
+  let vis = &input.vis;
+
+  let derive_dbg =
+    quote!(#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]);
+
+  match representation.repr {
+    Repr::Rust => unreachable!(),
+    repr @ (Repr::C | Repr::CWithDiscriminant(_)) => {
+      let integer = match repr {
+        Repr::C => quote!(::core::ffi::c_int),
+        Repr::CWithDiscriminant(integer) => quote!(#integer),
+        _ => unreachable!(),
+      };
+      let input_ident = &input.ident;
+
+      let bits_repr = Representation { repr: Repr::C, ..representation };
+
+      // the enum manually re-configured as the actual tagged union it represents,
+      // thus circumventing the requirements rust imposes on the tag even when using
+      // #[repr(C)] enum layout
+      // see: https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields
+      let bits_ty_ident = Ident::new(&format!("{input_ident}Bits"), input.span());
+
+      // the variants union part of the tagged union. These get put into a union which gets the
+      // AnyBitPattern derive applied to it, thus checking that the fields of the union obey the requriements of AnyBitPattern.
+      // The types that actually go in the union are one more level of indirection deep: we generate new structs for each variant
+      // (`variant_struct_definitions`) which themselves have the `CheckedBitPattern` derive applied, thus generating `{variant_struct_ident}Bits`
+      // structs, which are the ones that go into this union.
+      let variants_union_ident =
+        Ident::new(&format!("{}Variants", input.ident), input.span());
+
+      let variant_struct_idents = variants
+        .iter()
+        .map(|v| Ident::new(&format!("{input_ident}Variant{}", v.ident), v.span()));
+
+      let variant_struct_definitions =
+        variant_struct_idents.clone().zip(variants.iter()).map(|(variant_struct_ident, v)| {
+          let fields = v.fields.iter().map(|v| &v.ty);
+
+          quote! {
+            #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::CheckedBitPattern)]
+            #[repr(C)]
+            #vis struct #variant_struct_ident(#(#fields),*);
+          }
+        });
+
+      let union_fields =
+        variant_struct_idents.clone().zip(variants.iter()).map(|(variant_struct_ident, v)| {
+          let variant_struct_bits_ident =
+            Ident::new(&format!("{variant_struct_ident}Bits"), input.span());
+          let field_ident = &v.ident;
+          quote! {
+            #field_ident: #variant_struct_bits_ident
+          }
+        });
+
+      let variant_checks = variant_struct_idents
+        .clone()
+        .zip(VariantDiscriminantIterator::new(variants.iter()))
+        .zip(variants.iter())
+        .map(|((variant_struct_ident, discriminant), v)| -> Result<_> {
+          let discriminant = discriminant?;
+          let discriminant = LitInt::new(&discriminant.to_string(), v.span());
+          let ident = &v.ident;
+          Ok(quote! {
+            #discriminant => {
+              let payload = unsafe { &bits.payload.#ident };
+              <#variant_struct_ident as #crate_name::CheckedBitPattern>::is_valid_bit_pattern(payload)
+            }
+          })
+        })
+        .collect::<Result<Vec<_>>>()?;
+
+      Ok((
+        quote! {
+          #[doc = #GENERATED_TYPE_DOCUMENTATION]
+          #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::AnyBitPattern)]
+          #derive_dbg
+          #bits_repr
+          #vis struct #bits_ty_ident {
+            tag: #integer,
+            payload: #variants_union_ident,
+          }
+
+          #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::AnyBitPattern)]
+          #[repr(C)]
+          #[allow(non_snake_case)]
+          #vis union #variants_union_ident {
+            #(#union_fields,)*
+          }
+
+          #[cfg(not(target_arch = "spirv"))]
+          impl ::core::fmt::Debug for #variants_union_ident {
+            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
+              let mut debug_struct = ::core::fmt::Formatter::debug_struct(f, ::core::stringify!(#variants_union_ident));
+              ::core::fmt::DebugStruct::finish_non_exhaustive(&mut debug_struct)
+            }
+          }
+
+          #(#variant_struct_definitions)*
+        },
+        quote! {
+          type Bits = #bits_ty_ident;
+
+          #[inline]
+          #[allow(clippy::double_comparisons)]
+          fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
+            match bits.tag {
+              #(#variant_checks)*
+              _ => false,
+            }
+          }
+        },
+      ))
+    }
+    Repr::Transparent => {
+      if variants.len() != 1 {
+        bail!("enums with more than one variant cannot be transparent")
+      }
+
+      let variant = &variants[0];
+
+      let bits_ty = Ident::new(&format!("{}Bits", input.ident), input.span());
+      let fields = variant.fields.iter().map(|v| &v.ty);
+
+      Ok((
+        quote! {
+          #[doc = #GENERATED_TYPE_DOCUMENTATION]
+          #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::CheckedBitPattern)]
+          #[repr(C)]
+          #vis struct #bits_ty(#(#fields),*);
+        },
+        quote! {
+          type Bits = <#bits_ty as #crate_name::CheckedBitPattern>::Bits;
+
+          #[inline]
+          #[allow(clippy::double_comparisons)]
+          fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
+            <#bits_ty as #crate_name::CheckedBitPattern>::is_valid_bit_pattern(bits)
+          }
+        },
+      ))
+    }
+    Repr::Integer(integer) => {
+      let bits_repr = Representation { repr: Repr::C, ..representation };
+      let input_ident = &input.ident;
+
+      // the enum manually re-configured as the union it represents. such a union is the union of variants
+      // as a repr(c) struct with the discriminator type inserted at the beginning.
+      // in our case we union the `Bits` representation of each variant rather than the variant itself, which we generate
+      // via a nested `CheckedBitPattern` derive on the `variant_struct_definitions` generated below.
+      //
+      // see: https://doc.rust-lang.org/reference/type-layout.html#primitive-representation-of-enums-with-fields
+      let bits_ty_ident = Ident::new(&format!("{input_ident}Bits"), input.span());
+
+      let variant_struct_idents = variants
+        .iter()
+        .map(|v| Ident::new(&format!("{input_ident}Variant{}", v.ident), v.span()));
+
+      let variant_struct_definitions =
+        variant_struct_idents.clone().zip(variants.iter()).map(|(variant_struct_ident, v)| {
+          let fields = v.fields.iter().map(|v| &v.ty);
+
+          // adding the discriminant repr integer as first field, as described above
+          quote! {
+            #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::CheckedBitPattern)]
+            #[repr(C)]
+            #vis struct #variant_struct_ident(#integer, #(#fields),*);
+          }
+        });
+
+      let union_fields =
+        variant_struct_idents.clone().zip(variants.iter()).map(|(variant_struct_ident, v)| {
+          let variant_struct_bits_ident =
+            Ident::new(&format!("{variant_struct_ident}Bits"), input.span());
+          let field_ident = &v.ident;
+          quote! {
+            #field_ident: #variant_struct_bits_ident
+          }
+        });
+
+      let variant_checks = variant_struct_idents
+        .clone()
+        .zip(VariantDiscriminantIterator::new(variants.iter()))
+        .zip(variants.iter())
+        .map(|((variant_struct_ident, discriminant), v)| -> Result<_> {
+          let discriminant = discriminant?;
+          let discriminant = LitInt::new(&discriminant.to_string(), v.span());
+          let ident = &v.ident;
+          Ok(quote! {
+            #discriminant => {
+              let payload = unsafe { &bits.#ident };
+              <#variant_struct_ident as #crate_name::CheckedBitPattern>::is_valid_bit_pattern(payload)
+            }
+          })
+        })
+        .collect::<Result<Vec<_>>>()?;
+
+      Ok((
+        quote! {
+          #[doc = #GENERATED_TYPE_DOCUMENTATION]
+          #[derive(::core::clone::Clone, ::core::marker::Copy, #crate_name::AnyBitPattern)]
+          #bits_repr
+          #[allow(non_snake_case)]
+          #vis union #bits_ty_ident {
+            __tag: #integer,
+            #(#union_fields,)*
+          }
+
+          #[cfg(not(target_arch = "spirv"))]
+          impl ::core::fmt::Debug for #bits_ty_ident {
+            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
+              let mut debug_struct = ::core::fmt::Formatter::debug_struct(f, ::core::stringify!(#bits_ty_ident));
+              ::core::fmt::DebugStruct::field(&mut debug_struct, "tag", unsafe { &self.__tag });
+              ::core::fmt::DebugStruct::finish_non_exhaustive(&mut debug_struct)
+            }
+          }
+
+          #(#variant_struct_definitions)*
+        },
+        quote! {
+          type Bits = #bits_ty_ident;
+
+          #[inline]
+          #[allow(clippy::double_comparisons)]
+          fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
+            match unsafe { bits.__tag } {
+              #(#variant_checks)*
+              _ => false,
+            }
+          }
+        },
+      ))
+    }
+  }
+}
+
 /// Check that a struct has no padding by asserting that the size of the struct
 /// is equal to the sum of the size of it's fields
-fn generate_assert_no_padding(
-  input: &DeriveInput,
-) -> Result<TokenStream> {
+fn generate_assert_no_padding(input: &DeriveInput) -> Result<TokenStream> {
   let struct_type = &input.ident;
   let span = input.ident.span();
   let fields = get_fields(input)?;
@@ -516,12 +835,13 @@ fn generate_assert_no_padding(
     let size_rest =
       quote_spanned!(span => #( + ::core::mem::size_of::<#field_types>() )*);
 
-    quote_spanned!(span => #size_first#size_rest)
+    quote_spanned!(span => #size_first #size_rest)
   } else {
     quote_spanned!(span => 0)
   };
 
   Ok(quote_spanned! {span => const _: fn() = || {
+    #[doc(hidden)]
     struct TypeWithoutPadding([u8; #size_sum]);
     let _ = ::core::mem::transmute::<#struct_type, TypeWithoutPadding>;
   };})
@@ -529,7 +849,7 @@ fn generate_assert_no_padding(
 
 /// Check that all fields implement a given trait
 fn generate_fields_are_trait(
-  input: &DeriveInput, trait_: TokenStream,
+  input: &DeriveInput, trait_: syn::Path,
 ) -> Result<TokenStream> {
   let (impl_generics, _ty_generics, where_clause) =
     input.generics.split_for_impl();
@@ -538,6 +858,7 @@ fn generate_fields_are_trait(
   let field_types = get_field_types(&fields);
   Ok(quote_spanned! {span => #(const _: fn() = || {
       #[allow(clippy::missing_const_for_fn)]
+      #[doc(hidden)]
       fn check #impl_generics () #where_clause {
         fn assert_impl<T: #trait_>() {}
         assert_impl::<#field_types>();
@@ -557,13 +878,11 @@ fn get_ident_from_stream(tokens: TokenStream) -> Option<Ident> {
 /// get a simple #[foo(bar)] attribute, returning "bar"
 fn get_simple_attr(attributes: &[Attribute], attr_name: &str) -> Option<Ident> {
   for attr in attributes {
-    if let (AttrStyle::Outer, Some(outer_ident), Some(inner_ident)) = (
-      &attr.style,
-      attr.path.get_ident(),
-      get_ident_from_stream(attr.tokens.clone()),
-    ) {
-      if outer_ident.to_string() == attr_name {
-        return Some(inner_ident);
+    if let (AttrStyle::Outer, Meta::List(list)) = (&attr.style, &attr.meta) {
+      if list.path.is_ident(attr_name) {
+        if let Some(ident) = get_ident_from_stream(list.tokens.clone()) {
+          return Some(ident);
+        }
       }
     }
   }
@@ -574,28 +893,30 @@ fn get_simple_attr(attributes: &[Attribute], attr_name: &str) -> Option<Ident> {
 fn get_repr(attributes: &[Attribute]) -> Result<Representation> {
   attributes
     .iter()
-    .filter_map(|attr| if attr.path.is_ident("repr") {
-      Some(attr.parse_args::<Representation>())
-    } else {
-      None
+    .filter_map(|attr| {
+      if attr.path().is_ident("repr") {
+        Some(attr.parse_args::<Representation>())
+      } else {
+        None
+      }
     })
     .try_fold(Representation::default(), |a, b| {
       let b = b?;
       Ok(Representation {
         repr: match (a.repr, b.repr) {
-          | (a, Repr::Rust) => a,
-          | (Repr::Rust, b) => b,
-          | _ => bail!("conflicting representation hints"),
+          (a, Repr::Rust) => a,
+          (Repr::Rust, b) => b,
+          _ => bail!("conflicting representation hints"),
         },
         packed: match (a.packed, b.packed) {
-          | (a, None) => a,
-          | (None, b) => b,
-          | _ => bail!("conflicting representation hints"),
+          (a, None) => a,
+          (None, b) => b,
+          _ => bail!("conflicting representation hints"),
         },
         align: match (a.align, b.align) {
-          | (a, None) => a,
-          | (None, b) => b,
-          | _ => bail!("conflicting representation hints"),
+          (Some(a), Some(b)) => Some(cmp::max(a, b)),
+          (a, None) => a,
+          (None, b) => b,
         },
       })
     })
@@ -612,6 +933,8 @@ mk_repr! {
   I64 => i64,
   I128 => i128,
   U128 => u128,
+  Usize => usize,
+  Isize => isize,
 }
 // where
 macro_rules! mk_repr {(
@@ -619,107 +942,168 @@ macro_rules! mk_repr {(
     $Xn:ident => $xn:ident
   ),* $(,)?
 ) => (
-  #[derive(Clone, Copy, PartialEq)]
-  enum Repr {
-    Rust,
-    C,
-    Transparent,
+  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+  enum IntegerRepr {
     $($Xn),*
   }
 
-  impl Repr {
-    fn is_integer(self) -> bool {
-      match self {
-        Repr::Rust | Repr::C | Repr::Transparent => false,
-        _ => true,
+  impl<'a> TryFrom<&'a str> for IntegerRepr {
+    type Error = &'a str;
+
+    fn try_from(value: &'a str) -> std::result::Result<Self, &'a str> {
+      match value {
+        $(
+          stringify!($xn) => Ok(Self::$Xn),
+        )*
+        _ => Err(value),
       }
     }
+  }
 
-    fn as_integer_type(self) -> Option<TokenStream> {
+  impl ToTokens for IntegerRepr {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
       match self {
-        Repr::Rust | Repr::C | Repr::Transparent => None,
         $(
-          Repr::$Xn => Some(quote! { ::core::primitive::$xn }),
+          Self::$Xn => tokens.extend(quote!($xn)),
         )*
       }
     }
   }
+)}
+use mk_repr;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum Repr {
+  Rust,
+  C,
+  Transparent,
+  Integer(IntegerRepr),
+  CWithDiscriminant(IntegerRepr),
+}
 
-  #[derive(Clone, Copy)]
-  struct Representation {
-    packed: Option<u32>,
-    align: Option<u32>,
-    repr: Repr,
+impl Repr {
+  fn is_integer(&self) -> bool {
+    matches!(self, Self::Integer(..))
   }
 
-  impl Default for Representation {
-    fn default() -> Self {
-      Self { packed: None, align: None, repr: Repr::Rust }
+  fn as_integer(&self) -> Option<IntegerRepr> {
+    if let Self::Integer(v) = self {
+      Some(*v)
+    } else {
+      None
     }
   }
+}
 
-  impl Parse for Representation {
-    fn parse(input: ParseStream<'_>) -> Result<Representation> {
-      let mut ret = Representation::default();
-      while !input.is_empty() {
-        let keyword = input.parse::<Ident>()?;
-        // preëmptively call `.to_string()` *once* (rather than on `is_ident()`)
-        let keyword_str = keyword.to_string();
-        let new_repr = match keyword_str.as_str() {
-          "C" => Repr::C,
-          "transparent" => Repr::Transparent,
-          "packed" => {
-            ret.packed = Some(if input.peek(token::Paren) {
-              let contents; parenthesized!(contents in input);
-              LitInt::base10_parse::<u32>(&contents.parse()?)?
-            } else {
-              1
-            });
-            let _: Option<Token![,]> = input.parse()?;
-            continue;
-          },
-          "align" => {
-            let contents; parenthesized!(contents in input);
-            ret.align = Some(LitInt::base10_parse::<u32>(&contents.parse()?)?);
-            let _: Option<Token![,]> = input.parse()?;
-            continue;
-          },
-        $(
-          stringify!($xn) => Repr::$Xn,
-        )*
-          _ => return Err(input.error("unrecognized representation hint"))
-        };
-        if ::core::mem::replace(&mut ret.repr, new_repr) != Repr::Rust {
-          input.error("duplicate representation hint");
-        }
-        let _: Option<Token![,]> = input.parse()?;
-      }
-      Ok(ret)
-    }
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+struct Representation {
+  packed: Option<u32>,
+  align: Option<u32>,
+  repr: Repr,
+}
+
+impl Default for Representation {
+  fn default() -> Self {
+    Self { packed: None, align: None, repr: Repr::Rust }
   }
+}
 
-  impl ToTokens for Representation {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-      let repr = match self.repr {
-        Repr::Rust => None,
-        Repr::C => Some(quote!(C)),
-        Repr::Transparent => Some(quote!(transparent)),
-        $(
-          Repr::$Xn => Some(quote!($xn)),
-        )*
+impl Parse for Representation {
+  fn parse(input: ParseStream<'_>) -> Result<Representation> {
+    let mut ret = Representation::default();
+    while !input.is_empty() {
+      let keyword = input.parse::<Ident>()?;
+      // preëmptively call `.to_string()` *once* (rather than on `is_ident()`)
+      let keyword_str = keyword.to_string();
+      let new_repr = match keyword_str.as_str() {
+        "C" => Repr::C,
+        "transparent" => Repr::Transparent,
+        "packed" => {
+          ret.packed = Some(if input.peek(token::Paren) {
+            let contents;
+            parenthesized!(contents in input);
+            LitInt::base10_parse::<u32>(&contents.parse()?)?
+          } else {
+            1
+          });
+          let _: Option<Token![,]> = input.parse()?;
+          continue;
+        }
+        "align" => {
+          let contents;
+          parenthesized!(contents in input);
+          let new_align = LitInt::base10_parse::<u32>(&contents.parse()?)?;
+          ret.align = Some(
+            ret
+              .align
+              .map_or(new_align, |old_align| cmp::max(old_align, new_align)),
+          );
+          let _: Option<Token![,]> = input.parse()?;
+          continue;
+        }
+        ident => {
+          let primitive = IntegerRepr::try_from(ident)
+            .map_err(|_| input.error("unrecognized representation hint"))?;
+          Repr::Integer(primitive)
+        }
       };
-      let packed = self.packed.map(|p| quote!(packed(#p)));
-      let comma = if packed.is_some() && repr.is_some() {
-        Some(quote!(,))
-      } else {
-        None
+      ret.repr = match (ret.repr, new_repr) {
+        (Repr::Rust, new_repr) => {
+          // This is the first explicit repr.
+          new_repr
+        }
+        (Repr::C, Repr::Integer(integer))
+        | (Repr::Integer(integer), Repr::C) => {
+          // Both the C repr and an integer repr have been specified
+          // -> merge into a C wit discriminant.
+          Repr::CWithDiscriminant(integer)
+        }
+        (_, _) => {
+          return Err(input.error("duplicate representation hint"));
+        }
       };
-      tokens.extend(quote!(
-        #[repr( #repr #comma #packed )]
-      ));
+      let _: Option<Token![,]> = input.parse()?;
+    }
+    Ok(ret)
+  }
+}
+
+impl ToTokens for Representation {
+  fn to_tokens(&self, tokens: &mut TokenStream) {
+    let mut meta = Punctuated::<_, Token![,]>::new();
+
+    match self.repr {
+      Repr::Rust => {}
+      Repr::C => meta.push(quote!(C)),
+      Repr::Transparent => meta.push(quote!(transparent)),
+      Repr::Integer(primitive) => meta.push(quote!(#primitive)),
+      Repr::CWithDiscriminant(primitive) => {
+        meta.push(quote!(C));
+        meta.push(quote!(#primitive));
+      }
+    }
+
+    if let Some(packed) = self.packed.as_ref() {
+      let lit = LitInt::new(&packed.to_string(), Span::call_site());
+      meta.push(quote!(packed(#lit)));
+    }
+
+    if let Some(align) = self.align.as_ref() {
+      let lit = LitInt::new(&align.to_string(), Span::call_site());
+      meta.push(quote!(align(#lit)));
     }
+
+    tokens.extend(quote!(
+      #[repr(#meta)]
+    ));
   }
-)} use mk_repr;
+}
+
+fn enum_has_fields<'a>(
+  mut variants: impl Iterator<Item = &'a Variant>,
+) -> bool {
+  variants.any(|v| matches!(v.fields, Fields::Named(_) | Fields::Unnamed(_)))
+}
 
 struct VariantDiscriminantIterator<'a, I: Iterator<Item = &'a Variant> + 'a> {
   inner: I,
@@ -741,12 +1125,6 @@ impl<'a, I: Iterator<Item = &'a Variant> + 'a> Iterator
 
   fn next(&mut self) -> Option<Self::Item> {
     let variant = self.inner.next()?;
-    if !variant.fields.is_empty() {
-      return Some(Err(Error::new_spanned(
-        &variant.fields,
-        "Only fieldless enums are supported",
-      )));
-    }
 
     if let Some((_, discriminant)) = &variant.discriminant {
       let discriminant_value = match parse_int_expr(discriminant) {
@@ -767,9 +1145,135 @@ fn parse_int_expr(expr: &Expr) -> Result<i64> {
     Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }) => {
       parse_int_expr(expr).map(|int| -int)
     }
-    Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
-      int.base10_parse()
-    }
+    Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => int.base10_parse(),
+    Expr::Lit(ExprLit { lit: Lit::Byte(byte), .. }) => Ok(byte.value().into()),
     _ => bail!("Not an integer expression"),
   }
 }
+
+#[cfg(test)]
+mod tests {
+  use syn::parse_quote;
+
+  use super::{get_repr, IntegerRepr, Repr, Representation};
+
+  #[test]
+  fn parse_basic_repr() {
+    let attr = parse_quote!(#[repr(C)]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(repr, Representation { repr: Repr::C, ..Default::default() });
+
+    let attr = parse_quote!(#[repr(transparent)]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(
+      repr,
+      Representation { repr: Repr::Transparent, ..Default::default() }
+    );
+
+    let attr = parse_quote!(#[repr(u8)]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(
+      repr,
+      Representation {
+        repr: Repr::Integer(IntegerRepr::U8),
+        ..Default::default()
+      }
+    );
+
+    let attr = parse_quote!(#[repr(packed)]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(repr, Representation { packed: Some(1), ..Default::default() });
+
+    let attr = parse_quote!(#[repr(packed(1))]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(repr, Representation { packed: Some(1), ..Default::default() });
+
+    let attr = parse_quote!(#[repr(packed(2))]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(repr, Representation { packed: Some(2), ..Default::default() });
+
+    let attr = parse_quote!(#[repr(align(2))]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(repr, Representation { align: Some(2), ..Default::default() });
+  }
+
+  #[test]
+  fn parse_advanced_repr() {
+    let attr = parse_quote!(#[repr(align(4), align(2))]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(repr, Representation { align: Some(4), ..Default::default() });
+
+    let attr1 = parse_quote!(#[repr(align(1))]);
+    let attr2 = parse_quote!(#[repr(align(4))]);
+    let attr3 = parse_quote!(#[repr(align(2))]);
+    let repr = get_repr(&[attr1, attr2, attr3]).unwrap();
+    assert_eq!(repr, Representation { align: Some(4), ..Default::default() });
+
+    let attr = parse_quote!(#[repr(C, u8)]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(
+      repr,
+      Representation {
+        repr: Repr::CWithDiscriminant(IntegerRepr::U8),
+        ..Default::default()
+      }
+    );
+
+    let attr = parse_quote!(#[repr(u8, C)]);
+    let repr = get_repr(&[attr]).unwrap();
+    assert_eq!(
+      repr,
+      Representation {
+        repr: Repr::CWithDiscriminant(IntegerRepr::U8),
+        ..Default::default()
+      }
+    );
+  }
+}
+
+pub fn bytemuck_crate_name(input: &DeriveInput) -> TokenStream {
+  const ATTR_NAME: &'static str = "crate";
+
+  let mut crate_name = quote!(::bytemuck);
+  for attr in &input.attrs {
+    if !attr.path().is_ident("bytemuck") {
+      continue;
+    }
+
+    attr.parse_nested_meta(|meta| {
+      if meta.path.is_ident(ATTR_NAME) {
+        let expr: syn::Expr = meta.value()?.parse()?;
+        let mut value = &expr;
+        while let syn::Expr::Group(e) = value {
+          value = &e.expr;
+        }
+        if let syn::Expr::Lit(syn::ExprLit {
+          lit: syn::Lit::Str(lit), ..
+        }) = value
+        {
+          let suffix = lit.suffix();
+          if !suffix.is_empty() {
+            bail!(format!("Unexpected suffix `{}` on string literal", suffix))
+          }
+          let path: syn::Path = match lit.parse() {
+            Ok(path) => path,
+            Err(_) => {
+              bail!(format!("Failed to parse path: {:?}", lit.value()))
+            }
+          };
+          crate_name = path.into_token_stream();
+        } else {
+          bail!(
+            "Expected bytemuck `crate` attribute to be a string: `crate = \"...\"`",
+          )
+        }
+      }
+      Ok(())
+    }).unwrap();
+  }
+
+  return crate_name;
+}
+
+const GENERATED_TYPE_DOCUMENTATION: &str =
+  " `bytemuck`-generated type for internal purposes only.";
diff --git a/src/allocation.rs b/src/allocation.rs
index 8c29864..bdcf724 100644
--- a/src/allocation.rs
+++ b/src/allocation.rs
@@ -10,23 +10,27 @@
 //! ["extern_crate_alloc"]}`
 
 use super::*;
+#[cfg(target_has_atomic = "ptr")]
+use alloc::sync::Arc;
 use alloc::{
   alloc::{alloc_zeroed, Layout},
   boxed::Box,
   rc::Rc,
-  sync::Arc,
   vec,
   vec::Vec,
 };
-use core::convert::TryInto;
+use core::{
+  mem::ManuallyDrop,
+  ops::{Deref, DerefMut},
+};
 
-/// As [`try_cast_box`](try_cast_box), but unwraps for you.
+/// As [`try_cast_box`], but unwraps for you.
 #[inline]
 pub fn cast_box<A: NoUninit, B: AnyBitPattern>(input: Box<A>) -> Box<B> {
   try_cast_box(input).map_err(|(e, _v)| e).unwrap()
 }
 
-/// Attempts to cast the content type of a [`Box`](alloc::boxed::Box).
+/// Attempts to cast the content type of a [`Box`].
 ///
 /// On failure you get back an error along with the starting `Box`.
 ///
@@ -63,23 +67,11 @@ pub fn try_cast_box<A: NoUninit, B: AnyBitPattern>(
 #[inline]
 pub fn try_zeroed_box<T: Zeroable>() -> Result<Box<T>, ()> {
   if size_of::<T>() == 0 {
-    // This will not allocate but simple create a dangling slice pointer.
-    // NB: We go the way via a push to `Vec<T>` to ensure the compiler
-    // does not allocate space for T on the stack even if the branch
-    // would not be taken.
-    let mut vec = Vec::with_capacity(1);
-    vec.resize_with(1, || T::zeroed());
-    let ptr: Box<[T; 1]> = vec.into_boxed_slice().try_into().ok().unwrap();
-    debug_assert!(
-      align_of::<[T; 1]>() == align_of::<T>()
-        && size_of::<[T; 1]>() == size_of::<T>()
-    );
-    // NB: We basically do the same as in try_cast_box here:
-    let ptr: Box<T> = unsafe { Box::from_raw(Box::into_raw(ptr) as *mut _) };
-    return Ok(ptr);
-  }
-  let layout =
-    Layout::from_size_align(size_of::<T>(), align_of::<T>()).unwrap();
+    // This will not allocate but simply create a dangling pointer.
+    let dangling = core::ptr::NonNull::dangling().as_ptr();
+    return Ok(unsafe { Box::from_raw(dangling) });
+  }
+  let layout = Layout::new::<T>();
   let ptr = unsafe { alloc_zeroed(layout) };
   if ptr.is_null() {
     // we don't know what the error is because `alloc_zeroed` is a dumb API
@@ -131,15 +123,11 @@ pub fn zeroed_vec<T: Zeroable>(length: usize) -> Vec<T> {
 pub fn try_zeroed_slice_box<T: Zeroable>(
   length: usize,
 ) -> Result<Box<[T]>, ()> {
-  if size_of::<T>() == 0 {
-    // This will not allocate but simple create a dangling slice pointer.
-    let mut vec = Vec::with_capacity(length);
-    vec.resize_with(length, || T::zeroed());
-    return Ok(vec.into_boxed_slice());
-  }
-  if length == 0 {
-    // This will also not allocate.
-    return Ok(Vec::new().into_boxed_slice());
+  if size_of::<T>() == 0 || length == 0 {
+    // This will not allocate but simply create a dangling slice pointer.
+    let dangling = core::ptr::NonNull::dangling().as_ptr();
+    let dangling_slice = core::ptr::slice_from_raw_parts_mut(dangling, length);
+    return Ok(unsafe { Box::from_raw(dangling_slice) });
   }
   let layout = core::alloc::Layout::array::<T>(length).map_err(|_| ())?;
   let ptr = unsafe { alloc_zeroed(layout) };
@@ -153,12 +141,12 @@ pub fn try_zeroed_slice_box<T: Zeroable>(
   }
 }
 
-/// As [`try_zeroed_slice_box`](try_zeroed_slice_box), but unwraps for you.
+/// As [`try_zeroed_slice_box`], but unwraps for you.
 pub fn zeroed_slice_box<T: Zeroable>(length: usize) -> Box<[T]> {
   try_zeroed_slice_box(length).unwrap()
 }
 
-/// As [`try_cast_slice_box`](try_cast_slice_box), but unwraps for you.
+/// As [`try_cast_slice_box`], but unwraps for you.
 #[inline]
 pub fn cast_slice_box<A: NoUninit, B: AnyBitPattern>(
   input: Box<[A]>,
@@ -209,13 +197,13 @@ pub fn try_cast_slice_box<A: NoUninit, B: AnyBitPattern>(
   }
 }
 
-/// As [`try_cast_vec`](try_cast_vec), but unwraps for you.
+/// As [`try_cast_vec`], but unwraps for you.
 #[inline]
 pub fn cast_vec<A: NoUninit, B: AnyBitPattern>(input: Vec<A>) -> Vec<B> {
   try_cast_vec(input).map_err(|(e, _v)| e).unwrap()
 }
 
-/// Attempts to cast the content type of a [`Vec`](alloc::vec::Vec).
+/// Attempts to cast the content type of a [`Vec`].
 ///
 /// On failure you get back an error along with the starting `Vec`.
 ///
@@ -225,7 +213,7 @@ pub fn cast_vec<A: NoUninit, B: AnyBitPattern>(input: Vec<A>) -> Vec<B> {
 ///   alignment.
 /// * The start and end content size in bytes of the `Vec` must be the exact
 ///   same.
-/// * The start and end capacity in bytes of the `Vec` mest be the exact same.
+/// * The start and end capacity in bytes of the `Vec` must be the exact same.
 #[inline]
 pub fn try_cast_vec<A: NoUninit, B: AnyBitPattern>(
   input: Vec<A>,
@@ -298,13 +286,10 @@ pub fn try_cast_vec<A: NoUninit, B: AnyBitPattern>(
 ///   assert_eq!(&vec_of_words[..], &[0x0005_0006, 0x0007_0008][..])
 /// }
 /// ```
-pub fn pod_collect_to_vec<
-  A: NoUninit + AnyBitPattern,
-  B: NoUninit + AnyBitPattern,
->(
+pub fn pod_collect_to_vec<A: NoUninit, B: NoUninit + AnyBitPattern>(
   src: &[A],
 ) -> Vec<B> {
-  let src_size = size_of_val(src);
+  let src_size = core::mem::size_of_val(src);
   // Note(Lokathor): dst_count is rounded up so that the dest will always be at
   // least as many bytes as the src.
   let dst_count = src_size / size_of::<B>()
@@ -317,7 +302,7 @@ pub fn pod_collect_to_vec<
   dst
 }
 
-/// As [`try_cast_rc`](try_cast_rc), but unwraps for you.
+/// As [`try_cast_rc`], but unwraps for you.
 #[inline]
 pub fn cast_rc<A: NoUninit + AnyBitPattern, B: NoUninit + AnyBitPattern>(
   input: Rc<A>,
@@ -325,7 +310,7 @@ pub fn cast_rc<A: NoUninit + AnyBitPattern, B: NoUninit + AnyBitPattern>(
   try_cast_rc(input).map_err(|(e, _v)| e).unwrap()
 }
 
-/// Attempts to cast the content type of a [`Rc`](alloc::rc::Rc).
+/// Attempts to cast the content type of a [`Rc`].
 ///
 /// On failure you get back an error along with the starting `Rc`.
 ///
@@ -353,15 +338,16 @@ pub fn try_cast_rc<A: NoUninit + AnyBitPattern, B: NoUninit + AnyBitPattern>(
   }
 }
 
-/// As [`try_cast_arc`](try_cast_arc), but unwraps for you.
+/// As [`try_cast_arc`], but unwraps for you.
 #[inline]
+#[cfg(target_has_atomic = "ptr")]
 pub fn cast_arc<A: NoUninit + AnyBitPattern, B: NoUninit + AnyBitPattern>(
   input: Arc<A>,
 ) -> Arc<B> {
   try_cast_arc(input).map_err(|(e, _v)| e).unwrap()
 }
 
-/// Attempts to cast the content type of a [`Arc`](alloc::sync::Arc).
+/// Attempts to cast the content type of a [`Arc`].
 ///
 /// On failure you get back an error along with the starting `Arc`.
 ///
@@ -375,6 +361,7 @@ pub fn cast_arc<A: NoUninit + AnyBitPattern, B: NoUninit + AnyBitPattern>(
 ///   alignment.
 /// * The start and end size of the `Arc` must have the exact same size.
 #[inline]
+#[cfg(target_has_atomic = "ptr")]
 pub fn try_cast_arc<
   A: NoUninit + AnyBitPattern,
   B: NoUninit + AnyBitPattern,
@@ -392,7 +379,7 @@ pub fn try_cast_arc<
   }
 }
 
-/// As [`try_cast_slice_rc`](try_cast_slice_rc), but unwraps for you.
+/// As [`try_cast_slice_rc`], but unwraps for you.
 #[inline]
 pub fn cast_slice_rc<
   A: NoUninit + AnyBitPattern,
@@ -454,8 +441,9 @@ pub fn try_cast_slice_rc<
   }
 }
 
-/// As [`try_cast_slice_arc`](try_cast_slice_arc), but unwraps for you.
+/// As [`try_cast_slice_arc`], but unwraps for you.
 #[inline]
+#[cfg(target_has_atomic = "ptr")]
 pub fn cast_slice_arc<
   A: NoUninit + AnyBitPattern,
   B: NoUninit + AnyBitPattern,
@@ -480,6 +468,7 @@ pub fn cast_slice_arc<
 /// * The start and end content size in bytes of the `Arc<[T]>` must be the
 ///   exact same.
 #[inline]
+#[cfg(target_has_atomic = "ptr")]
 pub fn try_cast_slice_arc<
   A: NoUninit + AnyBitPattern,
   B: NoUninit + AnyBitPattern,
@@ -526,7 +515,7 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
     Self: Sized,
     Inner: Sized,
   {
-    let mut s = core::mem::ManuallyDrop::new(s);
+    let mut s = ManuallyDrop::new(s);
 
     let length = s.len();
     let capacity = s.capacity();
@@ -545,6 +534,11 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
   /// type.
   #[inline]
   fn wrap_box(s: Box<Inner>) -> Box<Self> {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is the best we can do to assert their metadata is the same type
+    // on stable.
     assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
 
     unsafe {
@@ -565,10 +559,14 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
     }
   }
 
-  /// Convert an [`Rc`](alloc::rc::Rc) to the inner type into an `Rc` to the
-  /// wrapper type.
+  /// Convert an [`Rc`] to the inner type into an `Rc` to the wrapper type.
   #[inline]
   fn wrap_rc(s: Rc<Inner>) -> Rc<Self> {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is the best we can do to assert their metadata is the same type
+    // on stable.
     assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
 
     unsafe {
@@ -587,10 +585,15 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
     }
   }
 
-  /// Convert an [`Arc`](alloc::sync::Arc) to the inner type into an `Arc` to
-  /// the wrapper type.
+  /// Convert an [`Arc`] to the inner type into an `Arc` to the wrapper type.
   #[inline]
+  #[cfg(target_has_atomic = "ptr")]
   fn wrap_arc(s: Arc<Inner>) -> Arc<Self> {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is the best we can do to assert their metadata is the same type
+    // on stable.
     assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
 
     unsafe {
@@ -615,7 +618,7 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
     Self: Sized,
     Inner: Sized,
   {
-    let mut s = core::mem::ManuallyDrop::new(s);
+    let mut s = ManuallyDrop::new(s);
 
     let length = s.len();
     let capacity = s.capacity();
@@ -634,6 +637,11 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
   /// type.
   #[inline]
   fn peel_box(s: Box<Self>) -> Box<Inner> {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is the best we can do to assert their metadata is the same type
+    // on stable.
     assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
 
     unsafe {
@@ -654,10 +662,14 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
     }
   }
 
-  /// Convert an [`Rc`](alloc::rc::Rc) to the wrapper type into an `Rc` to the
-  /// inner type.
+  /// Convert an [`Rc`] to the wrapper type into an `Rc` to the inner type.
   #[inline]
   fn peel_rc(s: Rc<Self>) -> Rc<Inner> {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is the best we can do to assert their metadata is the same type
+    // on stable.
     assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
 
     unsafe {
@@ -676,10 +688,15 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
     }
   }
 
-  /// Convert an [`Arc`](alloc::sync::Arc) to the wrapper type into an `Arc` to
-  /// the inner type.
+  /// Convert an [`Arc`] to the wrapper type into an `Arc` to the inner type.
   #[inline]
+  #[cfg(target_has_atomic = "ptr")]
   fn peel_arc(s: Arc<Self>) -> Arc<Inner> {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is the best we can do to assert their metadata is the same type
+    // on stable.
     assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
 
     unsafe {
@@ -698,4 +715,197 @@ pub trait TransparentWrapperAlloc<Inner: ?Sized>:
     }
   }
 }
-impl<I: ?Sized, T: TransparentWrapper<I>> TransparentWrapperAlloc<I> for T {}
+
+impl<I: ?Sized, T: ?Sized + TransparentWrapper<I>> TransparentWrapperAlloc<I>
+  for T
+{
+}
+
+/// 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.
+  ptr: NonNull<u8>,
+  layout: Layout,
+}
+
+impl Deref for BoxBytes {
+  type Target = [u8];
+
+  fn deref(&self) -> &Self::Target {
+    // SAFETY: See type invariant.
+    unsafe {
+      core::slice::from_raw_parts(self.ptr.as_ptr(), self.layout.size())
+    }
+  }
+}
+
+impl DerefMut for BoxBytes {
+  fn deref_mut(&mut self) -> &mut Self::Target {
+    // SAFETY: See type invariant.
+    unsafe {
+      core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.layout.size())
+    }
+  }
+}
+
+impl Drop for BoxBytes {
+  fn drop(&mut self) {
+    // SAFETY: See type invariant.
+    unsafe { alloc::alloc::dealloc(self.ptr.as_ptr(), self.layout) };
+  }
+}
+
+impl<T: ?Sized + sealed::BoxBytesOf> From<Box<T>> for BoxBytes {
+  fn from(value: Box<T>) -> Self {
+    value.box_bytes_of()
+  }
+}
+
+mod sealed {
+  use crate::{BoxBytes, PodCastError};
+  use alloc::boxed::Box;
+
+  pub trait BoxBytesOf {
+    fn box_bytes_of(self: Box<Self>) -> BoxBytes;
+  }
+
+  pub trait FromBoxBytes {
+    fn try_from_box_bytes(
+      bytes: BoxBytes,
+    ) -> Result<Box<Self>, (PodCastError, BoxBytes)>;
+  }
+}
+
+impl<T: NoUninit> sealed::BoxBytesOf for T {
+  fn box_bytes_of(self: Box<Self>) -> BoxBytes {
+    let layout = Layout::new::<T>();
+    let ptr = Box::into_raw(self) as *mut u8;
+    // SAFETY: Box::into_raw() returns a non-null pointer.
+    let ptr = unsafe { NonNull::new_unchecked(ptr) };
+    BoxBytes { ptr, layout }
+  }
+}
+
+impl<T: NoUninit> sealed::BoxBytesOf for [T] {
+  fn box_bytes_of(self: Box<Self>) -> BoxBytes {
+    let layout = Layout::for_value::<[T]>(&self);
+    let ptr = Box::into_raw(self) as *mut u8;
+    // SAFETY: Box::into_raw() returns a non-null pointer.
+    let ptr = unsafe { NonNull::new_unchecked(ptr) };
+    BoxBytes { ptr, layout }
+  }
+}
+
+impl sealed::BoxBytesOf for str {
+  fn box_bytes_of(self: Box<Self>) -> BoxBytes {
+    self.into_boxed_bytes().box_bytes_of()
+  }
+}
+
+impl<T: AnyBitPattern> sealed::FromBoxBytes for T {
+  fn try_from_box_bytes(
+    bytes: BoxBytes,
+  ) -> Result<Box<Self>, (PodCastError, BoxBytes)> {
+    let layout = Layout::new::<T>();
+    if bytes.layout.align() != layout.align() {
+      Err((PodCastError::AlignmentMismatch, bytes))
+    } else if bytes.layout.size() != layout.size() {
+      Err((PodCastError::SizeMismatch, bytes))
+    } else {
+      let (ptr, _) = bytes.into_raw_parts();
+      // SAFETY: See BoxBytes type invariant.
+      Ok(unsafe { Box::from_raw(ptr.as_ptr() as *mut T) })
+    }
+  }
+}
+
+impl<T: AnyBitPattern> sealed::FromBoxBytes for [T] {
+  fn try_from_box_bytes(
+    bytes: BoxBytes,
+  ) -> Result<Box<Self>, (PodCastError, BoxBytes)> {
+    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 {
+      Err((PodCastError::OutputSliceWouldHaveSlop, bytes))
+    } else {
+      let (ptr, layout) = bytes.into_raw_parts();
+      let length = layout.size() / single_layout.size();
+      let ptr =
+        core::ptr::slice_from_raw_parts_mut(ptr.as_ptr() as *mut T, length);
+      // SAFETY: See BoxBytes type invariant.
+      Ok(unsafe { Box::from_raw(ptr) })
+    }
+  }
+}
+
+/// Re-interprets `Box<T>` as `BoxBytes`.
+///
+/// `T` must be either [`Sized`] and [`NoUninit`],
+/// [`[U]`](slice) where `U: NoUninit`, or [`str`].
+#[inline]
+pub fn box_bytes_of<T: sealed::BoxBytesOf + ?Sized>(input: Box<T>) -> BoxBytes {
+  input.box_bytes_of()
+}
+
+/// Re-interprets `BoxBytes` as `Box<T>`.
+///
+/// `T` must be either [`Sized`] + [`AnyBitPattern`], or
+/// [`[U]`](slice) where `U: AnyBitPattern`.
+///
+/// ## Panics
+///
+/// This is [`try_from_box_bytes`] but will panic on error and the input will be
+/// dropped.
+#[inline]
+pub fn from_box_bytes<T: sealed::FromBoxBytes + ?Sized>(
+  input: BoxBytes,
+) -> Box<T> {
+  try_from_box_bytes(input).map_err(|(error, _)| error).unwrap()
+}
+
+/// Re-interprets `BoxBytes` as `Box<T>`.
+///
+/// `T` must be either [`Sized`] + [`AnyBitPattern`], or
+/// [`[U]`](slice) where `U: AnyBitPattern`.
+///
+/// Returns `Err`:
+/// * If the input isn't aligned for `T`.
+/// * If `T: Sized` and the input's length isn't exactly the size of `T`.
+/// * If `T = [U]` and the input's length isn't exactly a multiple of the size
+///   of `U`.
+#[inline]
+pub fn try_from_box_bytes<T: sealed::FromBoxBytes + ?Sized>(
+  input: BoxBytes,
+) -> Result<Box<T>, (PodCastError, BoxBytes)> {
+  T::try_from_box_bytes(input)
+}
+
+impl BoxBytes {
+  /// Constructs a `BoxBytes` from its raw parts.
+  ///
+  /// # Safety
+  ///
+  /// The pointer is owned, has been allocated with the provided layout, and
+  /// points to `layout.size()` initialized bytes.
+  pub unsafe fn from_raw_parts(ptr: NonNull<u8>, layout: Layout) -> Self {
+    BoxBytes { ptr, layout }
+  }
+
+  /// Deconstructs a `BoxBytes` into its raw parts.
+  ///
+  /// The pointer is owned, has been allocated with the provided layout, and
+  /// points to `layout.size()` initialized bytes.
+  pub fn into_raw_parts(self) -> (NonNull<u8>, Layout) {
+    let me = ManuallyDrop::new(self);
+    (me.ptr, me.layout)
+  }
+
+  /// Returns the original layout.
+  pub fn layout(&self) -> Layout {
+    self.layout
+  }
+}
diff --git a/src/anybitpattern.rs b/src/anybitpattern.rs
index 3a55e62..56c6bc6 100644
--- a/src/anybitpattern.rs
+++ b/src/anybitpattern.rs
@@ -14,7 +14,7 @@ use crate::{Pod, Zeroable};
 ///
 /// [`AnyBitPattern`] is a subset of [`Zeroable`], meaning that any `T:
 /// AnyBitPattern` is also [`Zeroable`], but any `T: Zeroable` is not
-/// necessarily [`AnyBitPattern  ]
+/// necessarily [`AnyBitPattern`]
 ///
 /// # Derive
 ///
@@ -54,3 +54,8 @@ pub unsafe trait AnyBitPattern:
 }
 
 unsafe impl<T: Pod> AnyBitPattern for T {}
+
+#[cfg(feature = "zeroable_maybe_uninit")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "zeroable_maybe_uninit")))]
+unsafe impl<T> AnyBitPattern for core::mem::MaybeUninit<T> where T: AnyBitPattern
+{}
diff --git a/src/checked.rs b/src/checked.rs
index 112abf0..cc65869 100644
--- a/src/checked.rs
+++ b/src/checked.rs
@@ -15,7 +15,7 @@ use crate::{
 /// To do this, we define a `Bits` type which is a type with equivalent layout
 /// to `Self` other than the invalid bit patterns which disallow `Self` from
 /// being [`AnyBitPattern`]. This `Bits` type must itself implement
-/// [`AnyBitPattern`]. Then, we implement a function that checks wheter a
+/// [`AnyBitPattern`]. Then, we implement a function that checks whether a
 /// certain instance of the `Bits` is also a valid bit pattern of `Self`. If
 /// this check passes, then we can allow casting from the `Bits` to `Self` (and
 /// therefore, any type which is able to be cast to `Bits` is also able to be
@@ -163,23 +163,54 @@ unsafe impl CheckedBitPattern for bool {
 
   #[inline]
   fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
-    match *bits {
-      0 | 1 => true,
-      _ => false,
-    }
+    matches!(*bits, 0 | 1)
   }
 }
 
+// Rust 1.70.0 documents that NonZero[int] has the same layout as [int].
+macro_rules! impl_checked_for_nonzero {
+  ($($nonzero:ty: $primitive:ty),* $(,)?) => {
+    $(
+      unsafe impl CheckedBitPattern for $nonzero {
+        type Bits = $primitive;
+
+        #[inline]
+        fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
+          *bits != 0
+        }
+      }
+    )*
+  };
+}
+impl_checked_for_nonzero! {
+  core::num::NonZeroU8: u8,
+  core::num::NonZeroI8: i8,
+  core::num::NonZeroU16: u16,
+  core::num::NonZeroI16: i16,
+  core::num::NonZeroU32: u32,
+  core::num::NonZeroI32: i32,
+  core::num::NonZeroU64: u64,
+  core::num::NonZeroI64: i64,
+  core::num::NonZeroI128: i128,
+  core::num::NonZeroU128: u128,
+  core::num::NonZeroUsize: usize,
+  core::num::NonZeroIsize: isize,
+}
+
 /// The things that can go wrong when casting between [`CheckedBitPattern`] data
 /// forms.
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum CheckedCastError {
   /// An error occurred during a true-[`Pod`] cast
+  ///
+  /// [`Pod`]: crate::Pod
   PodCastError(crate::PodCastError),
   /// When casting to a [`CheckedBitPattern`] type, it is possible that the
   /// original data contains an invalid bit pattern. If so, the cast will
   /// fail and this error will be returned. Will never happen on casts
   /// between [`Pod`] types.
+  ///
+  /// [`Pod`]: crate::Pod
   InvalidBitPattern,
 }
 
@@ -190,6 +221,7 @@ impl core::fmt::Display for CheckedCastError {
   }
 }
 #[cfg(feature = "extern_crate_std")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "extern_crate_std")))]
 impl std::error::Error for CheckedCastError {}
 
 impl From<crate::PodCastError> for CheckedCastError {
@@ -209,7 +241,7 @@ impl From<crate::PodCastError> for CheckedCastError {
 pub fn try_from_bytes<T: CheckedBitPattern>(
   s: &[u8],
 ) -> Result<&T, CheckedCastError> {
-  let pod = unsafe { internal::try_from_bytes(s) }?;
+  let pod = crate::try_from_bytes(s)?;
 
   if <T as CheckedBitPattern>::is_valid_bit_pattern(pod) {
     Ok(unsafe { &*(pod as *const <T as CheckedBitPattern>::Bits as *const T) })
@@ -247,9 +279,9 @@ pub fn try_from_bytes_mut<T: CheckedBitPattern + NoUninit>(
 pub fn try_pod_read_unaligned<T: CheckedBitPattern>(
   bytes: &[u8],
 ) -> Result<T, CheckedCastError> {
-  let pod = unsafe { internal::try_pod_read_unaligned(bytes) }?;
+  let pod = crate::try_pod_read_unaligned(bytes)?;
 
-  if <T as CheckedBitPattern>::is_valid_bit_pattern(pod) {
+  if <T as CheckedBitPattern>::is_valid_bit_pattern(&pod) {
     Ok(unsafe { transmute!(pod) })
   } else {
     Err(CheckedCastError::InvalidBitPattern)
@@ -271,7 +303,7 @@ pub fn try_pod_read_unaligned<T: CheckedBitPattern>(
 pub fn try_cast<A: NoUninit, B: CheckedBitPattern>(
   a: A,
 ) -> Result<B, CheckedCastError> {
-  let pod = unsafe { internal::try_cast(a) }?;
+  let pod = crate::try_cast(a)?;
 
   if <B as CheckedBitPattern>::is_valid_bit_pattern(&pod) {
     Ok(unsafe { transmute!(pod) })
@@ -291,7 +323,7 @@ pub fn try_cast<A: NoUninit, B: CheckedBitPattern>(
 pub fn try_cast_ref<A: NoUninit, B: CheckedBitPattern>(
   a: &A,
 ) -> Result<&B, CheckedCastError> {
-  let pod = unsafe { internal::try_cast_ref(a) }?;
+  let pod = crate::try_cast_ref(a)?;
 
   if <B as CheckedBitPattern>::is_valid_bit_pattern(pod) {
     Ok(unsafe { &*(pod as *const <B as CheckedBitPattern>::Bits as *const B) })
@@ -302,7 +334,7 @@ pub fn try_cast_ref<A: NoUninit, B: CheckedBitPattern>(
 
 /// Try to convert a `&mut T` into `&mut U`.
 ///
-/// As [`checked_cast_ref`], but `mut`.
+/// As [`try_cast_ref`], but `mut`.
 #[inline]
 pub fn try_cast_mut<
   A: NoUninit + AnyBitPattern,
@@ -340,7 +372,7 @@ pub fn try_cast_mut<
 pub fn try_cast_slice<A: NoUninit, B: CheckedBitPattern>(
   a: &[A],
 ) -> Result<&[B], CheckedCastError> {
-  let pod = unsafe { internal::try_cast_slice(a) }?;
+  let pod = crate::try_cast_slice(a)?;
 
   if pod.iter().all(|pod| <B as CheckedBitPattern>::is_valid_bit_pattern(pod)) {
     Ok(unsafe {
@@ -354,7 +386,7 @@ pub fn try_cast_slice<A: NoUninit, B: CheckedBitPattern>(
 /// Try to convert `&mut [A]` into `&mut [B]` (possibly with a change in
 /// length).
 ///
-/// As [`checked_cast_slice`], but `&mut`.
+/// As [`try_cast_slice`], but `&mut`.
 #[inline]
 pub fn try_cast_slice_mut<
   A: NoUninit + AnyBitPattern,
@@ -366,7 +398,7 @@ pub fn try_cast_slice_mut<
 
   if pod.iter().all(|pod| <B as CheckedBitPattern>::is_valid_bit_pattern(pod)) {
     Ok(unsafe {
-      core::slice::from_raw_parts_mut(pod.as_ptr() as *mut B, pod.len())
+      core::slice::from_raw_parts_mut(pod.as_mut_ptr() as *mut B, pod.len())
     })
   } else {
     Err(CheckedCastError::InvalidBitPattern)
@@ -404,7 +436,7 @@ pub fn from_bytes_mut<T: NoUninit + CheckedBitPattern>(s: &mut [u8]) -> &mut T {
 /// ## Panics
 /// * This is like `try_pod_read_unaligned` but will panic on failure.
 #[inline]
-pub fn pod_read_unaligned<T: AnyBitPattern>(bytes: &[u8]) -> T {
+pub fn pod_read_unaligned<T: CheckedBitPattern>(bytes: &[u8]) -> T {
   match try_pod_read_unaligned(bytes) {
     Ok(t) => t,
     Err(e) => something_went_wrong("pod_read_unaligned", e),
@@ -415,7 +447,7 @@ pub fn pod_read_unaligned<T: AnyBitPattern>(bytes: &[u8]) -> T {
 ///
 /// ## Panics
 ///
-/// * This is like [`try_cast`](try_cast), but will panic on a size mismatch.
+/// * This is like [`try_cast`], but will panic on a size mismatch.
 #[inline]
 pub fn cast<A: NoUninit, B: CheckedBitPattern>(a: A) -> B {
   match try_cast(a) {
diff --git a/src/contiguous.rs b/src/contiguous.rs
index 538514b..91308d0 100644
--- a/src/contiguous.rs
+++ b/src/contiguous.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::legacy_numeric_constants)]
+
 use super::*;
 
 /// A trait indicating that:
diff --git a/src/internal.rs b/src/internal.rs
index fd60779..3ede50f 100644
--- a/src/internal.rs
+++ b/src/internal.rs
@@ -23,7 +23,9 @@ possibility code branch.
 #[cfg(not(target_arch = "spirv"))]
 #[cold]
 #[inline(never)]
-pub(crate) fn something_went_wrong<D: core::fmt::Display>(_src: &str, _err: D) -> ! {
+pub(crate) fn something_went_wrong<D: core::fmt::Display>(
+  _src: &str, _err: D,
+) -> ! {
   // Note(Lokathor): Keeping the panic here makes the panic _formatting_ go
   // here too, which helps assembly readability and also helps keep down
   // the inline pressure.
@@ -128,6 +130,26 @@ pub(crate) unsafe fn pod_read_unaligned<T: Copy>(bytes: &[u8]) -> T {
   }
 }
 
+/// Checks if `ptr` is aligned to an `align` memory boundary.
+///
+/// ## Panics
+/// * If `align` is not a power of two. This includes when `align` is zero.
+#[inline]
+pub(crate) fn is_aligned_to(ptr: *const (), align: usize) -> bool {
+  #[cfg(feature = "align_offset")]
+  {
+    // This is in a way better than `ptr as usize % align == 0`,
+    // because casting a pointer to an integer has the side effect that it
+    // exposes the pointer's provenance, which may theoretically inhibit
+    // some compiler optimizations.
+    ptr.align_offset(align) == 0
+  }
+  #[cfg(not(feature = "align_offset"))]
+  {
+    ((ptr as usize) % align) == 0
+  }
+}
+
 /// Re-interprets `&[u8]` as `&T`.
 ///
 /// ## Failure
@@ -140,7 +162,7 @@ pub(crate) unsafe fn try_from_bytes<T: Copy>(
 ) -> Result<&T, PodCastError> {
   if s.len() != size_of::<T>() {
     Err(PodCastError::SizeMismatch)
-  } else if (s.as_ptr() as usize) % align_of::<T>() != 0 {
+  } else if !is_aligned_to(s.as_ptr() as *const (), align_of::<T>()) {
     Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
   } else {
     Ok(unsafe { &*(s.as_ptr() as *const T) })
@@ -159,7 +181,7 @@ pub(crate) unsafe fn try_from_bytes_mut<T: Copy>(
 ) -> Result<&mut T, PodCastError> {
   if s.len() != size_of::<T>() {
     Err(PodCastError::SizeMismatch)
-  } else if (s.as_ptr() as usize) % align_of::<T>() != 0 {
+  } else if !is_aligned_to(s.as_ptr() as *const (), align_of::<T>()) {
     Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
   } else {
     Ok(unsafe { &mut *(s.as_mut_ptr() as *mut T) })
@@ -282,7 +304,7 @@ pub(crate) unsafe fn try_cast_ref<A: Copy, B: Copy>(
   // Note(Lokathor): everything with `align_of` and `size_of` will optimize away
   // after monomorphization.
   if align_of::<B>() > align_of::<A>()
-    && (a as *const A as usize) % align_of::<B>() != 0
+    && !is_aligned_to(a as *const A as *const (), align_of::<B>())
   {
     Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
   } else if size_of::<B>() == size_of::<A>() {
@@ -302,7 +324,7 @@ pub(crate) unsafe fn try_cast_mut<A: Copy, B: Copy>(
   // Note(Lokathor): everything with `align_of` and `size_of` will optimize away
   // after monomorphization.
   if align_of::<B>() > align_of::<A>()
-    && (a as *mut A as usize) % align_of::<B>() != 0
+    && !is_aligned_to(a as *const A as *const (), align_of::<B>())
   {
     Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
   } else if size_of::<B>() == size_of::<A>() {
@@ -334,7 +356,7 @@ pub(crate) unsafe fn try_cast_slice<A: Copy, B: Copy>(
   // Note(Lokathor): everything with `align_of` and `size_of` will optimize away
   // after monomorphization.
   if align_of::<B>() > align_of::<A>()
-    && (a.as_ptr() as usize) % align_of::<B>() != 0
+    && !is_aligned_to(a.as_ptr() as *const (), align_of::<B>())
   {
     Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
   } else if size_of::<B>() == size_of::<A>() {
@@ -360,7 +382,7 @@ pub(crate) unsafe fn try_cast_slice_mut<A: Copy, B: Copy>(
   // Note(Lokathor): everything with `align_of` and `size_of` will optimize away
   // after monomorphization.
   if align_of::<B>() > align_of::<A>()
-    && (a.as_mut_ptr() as usize) % align_of::<B>() != 0
+    && !is_aligned_to(a.as_ptr() as *const (), align_of::<B>())
   {
     Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
   } else if size_of::<B>() == size_of::<A>() {
diff --git a/src/lib.rs b/src/lib.rs
index 6078689..3966627 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,16 @@
 #![no_std]
 #![warn(missing_docs)]
+#![allow(clippy::match_like_matches_macro)]
+#![allow(clippy::uninlined_format_args)]
+#![cfg_attr(feature = "nightly_docs", feature(doc_cfg))]
 #![cfg_attr(feature = "nightly_portable_simd", feature(portable_simd))]
+#![cfg_attr(
+  all(
+    feature = "nightly_stdsimd",
+    any(target_arch = "x86_64", target_arch = "x86")
+  ),
+  feature(stdarch_x86_avx512)
+)]
 
 //! This crate gives small utilities for casting between plain data types.
 //!
@@ -15,28 +25,65 @@
 //! * `&[T]` uses [`cast_slice`]
 //! * `&mut [T]` uses [`cast_slice_mut`]
 //!
-//! Some casts will never fail (eg: `cast::<u32, f32>` always works), other
-//! casts might fail (eg: `cast_ref::<[u8; 4], u32>` will fail if the reference
-//! isn't already aligned to 4). Each casting function has a "try" version which
-//! will return a `Result`, and the "normal" version which will simply panic on
-//! invalid input.
+//! Depending on the function, the [`NoUninit`] and/or [`AnyBitPattern`] traits
+//! are used to maintain memory safety.
+//!
+//! **Historical Note:** When the crate first started the [`Pod`] trait was used
+//! instead, and so you may hear people refer to that, but it has the strongest
+//! requirements and people eventually wanted the more fine-grained system, so
+//! here we are. All types that impl `Pod` have a blanket impl to also support
+//! `NoUninit` and `AnyBitPattern`. The traits unfortunately do not have a
+//! perfectly clean hierarchy for semver reasons.
+//!
+//! ## Failures
+//!
+//! Some casts will never fail, and other casts might fail.
+//!
+//! * `cast::<u32, f32>` always works (and [`f32::from_bits`]).
+//! * `cast_ref::<[u8; 4], u32>` might fail if the specific array reference
+//!   given at runtime doesn't have alignment 4.
+//!
+//! In addition to the "normal" forms of each function, which will panic on
+//! invalid input, there's also `try_` versions which will return a `Result`.
+//!
+//! If you would like to statically ensure that a cast will work at runtime you
+//! can use the `must_cast` crate feature and the `must_` casting functions. A
+//! "must cast" that can't be statically known to be valid will cause a
+//! compilation error (and sometimes a very hard to read compilation error).
 //!
 //! ## Using Your Own Types
 //!
-//! All the functions here are guarded by the [`Pod`] trait, which is a
+//! All the functions listed above are guarded by the [`Pod`] trait, which is a
 //! sub-trait of the [`Zeroable`] trait.
 //!
-//! If you're very sure that your type is eligible, you can implement those
-//! traits for your type and then they'll have full casting support. However,
-//! these traits are `unsafe`, and you should carefully read the requirements
-//! before adding the them to your own types.
+//! If you enable the crate's `derive` feature then these traits can be derived
+//! on your own types. The derive macros will perform the necessary checks on
+//! your type declaration, and trigger an error if your type does not qualify.
+//!
+//! The derive macros might not cover all edge cases, and sometimes they will
+//! error when actually everything is fine. As a last resort you can impl these
+//! traits manually. However, these traits are `unsafe`, and you should
+//! carefully read the requirements before using a manual implementation.
+//!
+//! ## Cargo Features
 //!
-//! ## Features
+//! The crate supports Rust 1.34 when no features are enabled, and so there's
+//! cargo features for thing that you might consider "obvious".
 //!
-//! * This crate is core only by default, but if you're using Rust 1.36 or later
-//!   you can enable the `extern_crate_alloc` cargo feature for some additional
-//!   methods related to `Box` and `Vec`. Note that the `docs.rs` documentation
-//!   is always built with `extern_crate_alloc` cargo feature enabled.
+//! The cargo features **do not** promise any particular MSRV, and they may
+//! increase their MSRV in new versions.
+//!
+//! * `derive`: Provide derive macros for the various traits.
+//! * `extern_crate_alloc`: Provide utilities for `alloc` related types such as
+//!   Box and Vec.
+//! * `zeroable_maybe_uninit` and `zeroable_atomics`: Provide more [`Zeroable`]
+//!   impls.
+//! * `wasm_simd` and `aarch64_simd`: Support more SIMD types.
+//! * `min_const_generics`: Provides appropriate impls for arrays of all lengths
+//!   instead of just for a select list of array lengths.
+//! * `must_cast`: Provides the `must_` functions, which will compile error if
+//!   the requested cast can't be statically verified.
+//! * `const_zeroed`: Provides a const version of the `zeroed` function.
 
 #[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
 use core::arch::aarch64;
@@ -47,7 +94,12 @@ use core::arch::x86;
 #[cfg(target_arch = "x86_64")]
 use core::arch::x86_64;
 //
-use core::{marker::*, mem::*, num::*, ptr::*};
+use core::{
+  marker::*,
+  mem::{align_of, size_of},
+  num::*,
+  ptr::*,
+};
 
 // Used from macros to ensure we aren't using some locally defined name and
 // actually are referencing libcore. This also would allow pre-2018 edition
@@ -70,12 +122,28 @@ macro_rules! transmute {
   };
 }
 
+/// A macro to implement marker traits for various simd types.
+/// #[allow(unused)] because the impls are only compiled on relevant platforms
+/// with relevant cargo features enabled.
+#[allow(unused)]
+macro_rules! impl_unsafe_marker_for_simd {
+  ($(#[cfg($cfg_predicate:meta)])? unsafe impl $trait:ident for $platform:ident :: {}) => {};
+  ($(#[cfg($cfg_predicate:meta)])? unsafe impl $trait:ident for $platform:ident :: { $first_type:ident $(, $types:ident)* $(,)? }) => {
+    $( #[cfg($cfg_predicate)] )?
+    $( #[cfg_attr(feature = "nightly_docs", doc(cfg($cfg_predicate)))] )?
+    unsafe impl $trait for $platform::$first_type {}
+    $( #[cfg($cfg_predicate)] )? // To prevent recursion errors if nothing is going to be expanded anyway.
+    impl_unsafe_marker_for_simd!($( #[cfg($cfg_predicate)] )? unsafe impl $trait for $platform::{ $( $types ),* });
+  };
+}
+
 #[cfg(feature = "extern_crate_std")]
 extern crate std;
 
 #[cfg(feature = "extern_crate_alloc")]
 extern crate alloc;
 #[cfg(feature = "extern_crate_alloc")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "extern_crate_alloc")))]
 pub mod allocation;
 #[cfg(feature = "extern_crate_alloc")]
 pub use allocation::*;
@@ -98,6 +166,12 @@ pub use pod::*;
 mod pod_in_option;
 pub use pod_in_option::*;
 
+#[cfg(feature = "must_cast")]
+mod must;
+#[cfg(feature = "must_cast")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "must_cast")))]
+pub use must::*;
+
 mod no_uninit;
 pub use no_uninit::*;
 
@@ -105,15 +179,17 @@ mod contiguous;
 pub use contiguous::*;
 
 mod offset_of;
-pub use offset_of::*;
+// ^ no import, the module only has a macro_rules, which are cursed and don't
+// follow normal import/export rules.
 
 mod transparent;
 pub use transparent::*;
 
 #[cfg(feature = "derive")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "derive")))]
 pub use bytemuck_derive::{
-  AnyBitPattern, CheckedBitPattern, Contiguous, NoUninit, Pod,
-  TransparentWrapper, Zeroable,
+  AnyBitPattern, ByteEq, ByteHash, CheckedBitPattern, Contiguous, NoUninit,
+  Pod, TransparentWrapper, Zeroable,
 };
 
 /// The things that can go wrong when casting between [`Pod`] data forms.
@@ -145,6 +221,7 @@ impl core::fmt::Display for PodCastError {
   }
 }
 #[cfg(feature = "extern_crate_std")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "extern_crate_std")))]
 impl std::error::Error for PodCastError {}
 
 /// Re-interprets `&T` as `&[u8]`.
@@ -169,7 +246,7 @@ pub fn bytes_of_mut<T: NoUninit + AnyBitPattern>(t: &mut T) -> &mut [u8] {
 ///
 /// ## Panics
 ///
-/// This is [`try_from_bytes`] but will panic on error.
+/// This is like [`try_from_bytes`] but will panic on error.
 #[inline]
 pub fn from_bytes<T: AnyBitPattern>(s: &[u8]) -> &T {
   unsafe { internal::from_bytes(s) }
@@ -179,7 +256,7 @@ pub fn from_bytes<T: AnyBitPattern>(s: &[u8]) -> &T {
 ///
 /// ## Panics
 ///
-/// This is [`try_from_bytes_mut`] but will panic on error.
+/// This is like [`try_from_bytes_mut`] but will panic on error.
 #[inline]
 pub fn from_bytes_mut<T: NoUninit + AnyBitPattern>(s: &mut [u8]) -> &mut T {
   unsafe { internal::from_bytes_mut(s) }
@@ -187,6 +264,9 @@ pub fn from_bytes_mut<T: NoUninit + AnyBitPattern>(s: &mut [u8]) -> &mut T {
 
 /// Reads from the bytes as if they were a `T`.
 ///
+/// Unlike [`from_bytes`], the slice doesn't need to respect alignment of `T`,
+/// only sizes must match.
+///
 /// ## Failure
 /// * If the `bytes` length is not equal to `size_of::<T>()`.
 #[inline]
@@ -198,6 +278,9 @@ pub fn try_pod_read_unaligned<T: AnyBitPattern>(
 
 /// Reads the slice into a `T` value.
 ///
+/// Unlike [`from_bytes`], the slice doesn't need to respect alignment of `T`,
+/// only sizes must match.
+///
 /// ## Panics
 /// * This is like `try_pod_read_unaligned` but will panic on failure.
 #[inline]
@@ -233,7 +316,7 @@ pub fn try_from_bytes_mut<T: NoUninit + AnyBitPattern>(
 ///
 /// ## Panics
 ///
-/// * This is like [`try_cast`](try_cast), but will panic on a size mismatch.
+/// * This is like [`try_cast`], but will panic on a size mismatch.
 #[inline]
 pub fn cast<A: NoUninit, B: AnyBitPattern>(a: A) -> B {
   unsafe { internal::cast(a) }
@@ -286,7 +369,8 @@ pub fn cast_slice_mut<
   unsafe { internal::cast_slice_mut(a) }
 }
 
-/// As `align_to`, but safe because of the [`Pod`] bound.
+/// As [`align_to`](https://doc.rust-lang.org/std/primitive.slice.html#method.align_to),
+/// but safe because of the [`Pod`] bound.
 #[inline]
 pub fn pod_align_to<T: NoUninit, U: AnyBitPattern>(
   vals: &[T],
@@ -294,7 +378,8 @@ pub fn pod_align_to<T: NoUninit, U: AnyBitPattern>(
   unsafe { vals.align_to::<U>() }
 }
 
-/// As `align_to_mut`, but safe because of the [`Pod`] bound.
+/// As [`align_to_mut`](https://doc.rust-lang.org/std/primitive.slice.html#method.align_to_mut),
+/// but safe because of the [`Pod`] bound.
 #[inline]
 pub fn pod_align_to_mut<
   T: NoUninit + AnyBitPattern,
@@ -383,3 +468,55 @@ pub fn try_cast_slice_mut<
 ) -> Result<&mut [B], PodCastError> {
   unsafe { internal::try_cast_slice_mut(a) }
 }
+
+/// Fill all bytes of `target` with zeroes (see [`Zeroable`]).
+///
+/// This is similar to `*target = Zeroable::zeroed()`, but guarantees that any
+/// padding bytes in `target` are zeroed as well.
+///
+/// See also [`fill_zeroes`], if you have a slice rather than a single value.
+#[inline]
+pub fn write_zeroes<T: Zeroable>(target: &mut T) {
+  struct EnsureZeroWrite<T>(*mut T);
+  impl<T> Drop for EnsureZeroWrite<T> {
+    #[inline(always)]
+    fn drop(&mut self) {
+      unsafe {
+        core::ptr::write_bytes(self.0, 0u8, 1);
+      }
+    }
+  }
+  unsafe {
+    let guard = EnsureZeroWrite(target);
+    core::ptr::drop_in_place(guard.0);
+    drop(guard);
+  }
+}
+
+/// Fill all bytes of `slice` with zeroes (see [`Zeroable`]).
+///
+/// This is similar to `slice.fill(Zeroable::zeroed())`, but guarantees that any
+/// padding bytes in `slice` are zeroed as well.
+///
+/// See also [`write_zeroes`], which zeroes all bytes of a single value rather
+/// than a slice.
+#[inline]
+pub fn fill_zeroes<T: Zeroable>(slice: &mut [T]) {
+  if core::mem::needs_drop::<T>() {
+    // If `T` needs to be dropped then we have to do this one item at a time, in
+    // case one of the intermediate drops does a panic.
+    slice.iter_mut().for_each(write_zeroes);
+  } else {
+    // Otherwise we can be really fast and just fill everthing with zeros.
+    let len = core::mem::size_of_val::<[T]>(slice);
+    unsafe { core::ptr::write_bytes(slice.as_mut_ptr() as *mut u8, 0u8, len) }
+  }
+}
+
+/// Same as [`Zeroable::zeroed`], but as a `const fn` const.
+#[cfg(feature = "const_zeroed")]
+#[inline]
+#[must_use]
+pub const fn zeroed<T: Zeroable>() -> T {
+  unsafe { core::mem::zeroed() }
+}
diff --git a/src/must.rs b/src/must.rs
new file mode 100644
index 0000000..96661be
--- /dev/null
+++ b/src/must.rs
@@ -0,0 +1,186 @@
+#![allow(clippy::module_name_repetitions)]
+#![allow(clippy::let_unit_value)]
+#![allow(clippy::let_underscore_untyped)]
+#![allow(clippy::ptr_as_ptr)]
+
+use crate::{AnyBitPattern, NoUninit};
+use core::mem::{align_of, size_of};
+
+struct Cast<A, B>((A, B));
+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)
+  );
+}
+
+/// Cast `A` into `B` if infalliable, or fail to compile.
+///
+/// Note that for this particular type of cast, alignment isn't a factor. The
+/// input value is semantically copied into the function and then returned to a
+/// new memory location which will have whatever the required alignment of the
+/// output type is.
+///
+/// ## Failure
+///
+/// * If the types don't have the same size this fails to compile.
+///
+/// ## Examples
+/// ```
+/// // compiles:
+/// let bytes: [u8; 2] = bytemuck::must_cast(12_u16);
+/// ```
+/// ```compile_fail,E0080
+/// // fails to compile (size mismatch):
+/// let bytes : [u8; 3] = bytemuck::must_cast(12_u16);
+/// ```
+#[inline]
+pub fn must_cast<A: NoUninit, B: AnyBitPattern>(a: A) -> B {
+  let _ = Cast::<A, B>::ASSERT_SIZE_EQUAL;
+  unsafe { transmute!(a) }
+}
+
+/// Convert `&A` into `&B` if infalliable, or fail to compile.
+///
+/// ## Failure
+///
+/// * If the target type has a greater alignment requirement.
+/// * If the source type and target type aren't the same size.
+///
+/// ## Examples
+/// ```
+/// // compiles:
+/// let bytes: &[u8; 2] = bytemuck::must_cast_ref(&12_u16);
+/// ```
+/// ```compile_fail,E0080
+/// // fails to compile (size mismatch):
+/// let bytes : &[u8; 3] = bytemuck::must_cast_ref(&12_u16);
+/// ```
+/// ```compile_fail,E0080
+/// // fails to compile (alignment requirements increased):
+/// let bytes : &u16 = bytemuck::must_cast_ref(&[1u8, 2u8]);
+/// ```
+#[inline]
+pub fn must_cast_ref<A: NoUninit, B: AnyBitPattern>(a: &A) -> &B {
+  let _ = Cast::<A, B>::ASSERT_SIZE_EQUAL;
+  let _ = Cast::<A, B>::ASSERT_ALIGN_GREATER_THAN_EQUAL;
+  unsafe { &*(a as *const A as *const B) }
+}
+
+/// Convert a `&mut A` into `&mut B` if infalliable, or fail to compile.
+///
+/// As [`must_cast_ref`], but `mut`.
+///
+/// ## Examples
+/// ```
+/// let mut i = 12_u16;
+/// // compiles:
+/// let bytes: &mut [u8; 2] = bytemuck::must_cast_mut(&mut i);
+/// ```
+/// ```compile_fail,E0080
+/// # let mut bytes: &mut [u8; 2] = &mut [1, 2];
+/// // fails to compile (alignment requirements increased):
+/// let i : &mut u16 = bytemuck::must_cast_mut(bytes);
+/// ```
+/// ```compile_fail,E0080
+/// # let mut i = 12_u16;
+/// // fails to compile (size mismatch):
+/// let bytes : &mut [u8; 3] = bytemuck::must_cast_mut(&mut i);
+/// ```
+#[inline]
+pub fn must_cast_mut<
+  A: NoUninit + AnyBitPattern,
+  B: NoUninit + AnyBitPattern,
+>(
+  a: &mut A,
+) -> &mut B {
+  let _ = Cast::<A, B>::ASSERT_SIZE_EQUAL;
+  let _ = Cast::<A, B>::ASSERT_ALIGN_GREATER_THAN_EQUAL;
+  unsafe { &mut *(a as *mut A as *mut B) }
+}
+
+/// Convert `&[A]` into `&[B]` (possibly with a change in length) if
+/// infalliable, or fail to compile.
+///
+/// * `input.as_ptr() as usize == output.as_ptr() as usize`
+/// * `input.len() * size_of::<A>() == output.len() * size_of::<B>()`
+///
+/// ## Failure
+///
+/// * 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.
+///
+/// ## Examples
+/// ```
+/// let indicies: &[u16] = &[1, 2, 3];
+/// // compiles:
+/// let bytes: &[u8] = bytemuck::must_cast_slice(indicies);
+/// ```
+/// ```compile_fail,E0080
+/// # let bytes : &[u8] = &[1, 0, 2, 0, 3, 0];
+/// // fails to compile (bytes.len() might not be a multiple of 2):
+/// let byte_pairs : &[[u8; 2]] = bytemuck::must_cast_slice(bytes);
+/// ```
+/// ```compile_fail,E0080
+/// # let byte_pairs : &[[u8; 2]] = &[[1, 0], [2, 0], [3, 0]];
+/// // fails to compile (alignment requirements increased):
+/// let indicies : &[u16] = bytemuck::must_cast_slice(byte_pairs);
+/// ```
+#[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_ALIGN_GREATER_THAN_EQUAL;
+  let new_len = if size_of::<A>() == size_of::<B>() {
+    a.len()
+  } else {
+    a.len() * (size_of::<A>() / size_of::<B>())
+  };
+  unsafe { core::slice::from_raw_parts(a.as_ptr() as *const B, new_len) }
+}
+
+/// Convert `&mut [A]` into `&mut [B]` (possibly with a change in length) if
+/// infalliable, or fail to compile.
+///
+/// As [`must_cast_slice`], but `&mut`.
+///
+/// ## Examples
+/// ```
+/// let mut indicies = [1, 2, 3];
+/// let indicies: &mut [u16] = &mut indicies;
+/// // compiles:
+/// let bytes: &mut [u8] = bytemuck::must_cast_slice_mut(indicies);
+/// ```
+/// ```compile_fail,E0080
+/// # let mut bytes = [1, 0, 2, 0, 3, 0];
+/// # let bytes : &mut [u8] = &mut bytes[..];
+/// // fails to compile (bytes.len() might not be a multiple of 2):
+/// let byte_pairs : &mut [[u8; 2]] = bytemuck::must_cast_slice_mut(bytes);
+/// ```
+/// ```compile_fail,E0080
+/// # let mut byte_pairs = [[1, 0], [2, 0], [3, 0]];
+/// # let byte_pairs : &mut [[u8; 2]] = &mut byte_pairs[..];
+/// // fails to compile (alignment requirements increased):
+/// let indicies : &mut [u16] = bytemuck::must_cast_slice_mut(byte_pairs);
+/// ```
+#[inline]
+pub fn must_cast_slice_mut<
+  A: NoUninit + AnyBitPattern,
+  B: NoUninit + AnyBitPattern,
+>(
+  a: &mut [A],
+) -> &mut [B] {
+  let _ = Cast::<A, B>::ASSERT_SIZE_MULTIPLE_OF;
+  let _ = Cast::<A, B>::ASSERT_ALIGN_GREATER_THAN_EQUAL;
+  let new_len = if size_of::<A>() == size_of::<B>() {
+    a.len()
+  } else {
+    a.len() * (size_of::<A>() / size_of::<B>())
+  };
+  unsafe { core::slice::from_raw_parts_mut(a.as_mut_ptr() as *mut B, new_len) }
+}
diff --git a/src/pod.rs b/src/pod.rs
index 08a9e94..724c0a0 100644
--- a/src/pod.rs
+++ b/src/pod.rs
@@ -54,19 +54,32 @@ unsafe impl Pod for f64 {}
 unsafe impl<T: Pod> Pod for Wrapping<T> {}
 
 #[cfg(feature = "unsound_ptr_pod_impl")]
+#[cfg_attr(
+  feature = "nightly_docs",
+  doc(cfg(feature = "unsound_ptr_pod_impl"))
+)]
 unsafe impl<T: 'static> Pod for *mut T {}
 #[cfg(feature = "unsound_ptr_pod_impl")]
+#[cfg_attr(
+  feature = "nightly_docs",
+  doc(cfg(feature = "unsound_ptr_pod_impl"))
+)]
 unsafe impl<T: 'static> Pod for *const T {}
 #[cfg(feature = "unsound_ptr_pod_impl")]
+#[cfg_attr(
+  feature = "nightly_docs",
+  doc(cfg(feature = "unsound_ptr_pod_impl"))
+)]
 unsafe impl<T: 'static> PodInOption for NonNull<T> {}
 
-unsafe impl<T: Pod> Pod for PhantomData<T> {}
+unsafe impl<T: ?Sized + 'static> Pod for PhantomData<T> {}
 unsafe impl Pod for PhantomPinned {}
-unsafe impl<T: Pod> Pod for ManuallyDrop<T> {}
+unsafe impl<T: Pod> Pod for core::mem::ManuallyDrop<T> {}
 
 // Note(Lokathor): MaybeUninit can NEVER be Pod.
 
 #[cfg(feature = "min_const_generics")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "min_const_generics")))]
 unsafe impl<T, const N: usize> Pod for [T; N] where T: Pod {}
 
 #[cfg(not(feature = "min_const_generics"))]
@@ -76,248 +89,77 @@ impl_unsafe_marker_for_array!(
   512, 1024, 2048, 4096
 );
 
-#[cfg(all(target_arch = "wasm32", feature = "wasm_simd"))]
-unsafe impl Pod for wasm32::v128 {}
+impl_unsafe_marker_for_simd!(
+  #[cfg(all(target_arch = "wasm32", feature = "wasm_simd"))]
+  unsafe impl Pod for wasm32::{v128}
+);
 
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float32x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float32x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float32x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float32x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float32x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float32x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float32x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float32x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float64x1_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float64x1x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float64x1x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float64x1x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float64x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float64x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float64x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::float64x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int16x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int16x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int16x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int16x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int16x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int16x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int16x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int32x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int32x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int32x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int32x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int32x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int32x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int32x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int32x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int64x1_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int64x1x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int64x1x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int64x1x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int64x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int64x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int64x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int64x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int8x16_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int8x16x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int8x16x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int8x16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int8x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int8x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int8x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::int8x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly16x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly16x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly16x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly16x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly16x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly16x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly16x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly64x1_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly64x1x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly64x1x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly64x1x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly64x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly64x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly64x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly64x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly8x16_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly8x16x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly8x16x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly8x16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly8x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly8x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly8x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::poly8x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint16x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint16x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint16x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint16x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint16x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint16x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint16x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint32x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint32x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint32x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint32x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint32x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint32x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint32x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint32x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint64x1_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint64x1x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint64x1x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint64x1x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint64x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint64x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint64x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint64x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint8x16_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint8x16x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint8x16x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint8x16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint8x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint8x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint8x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Pod for aarch64::uint8x8x4_t {}
+impl_unsafe_marker_for_simd!(
+  #[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
+  unsafe impl Pod for aarch64::{
+    float32x2_t, float32x2x2_t, float32x2x3_t, float32x2x4_t, float32x4_t,
+    float32x4x2_t, float32x4x3_t, float32x4x4_t, float64x1_t, float64x1x2_t,
+    float64x1x3_t, float64x1x4_t, float64x2_t, float64x2x2_t, float64x2x3_t,
+    float64x2x4_t, int16x4_t, int16x4x2_t, int16x4x3_t, int16x4x4_t, int16x8_t,
+    int16x8x2_t, int16x8x3_t, int16x8x4_t, int32x2_t, int32x2x2_t, int32x2x3_t,
+    int32x2x4_t, int32x4_t, int32x4x2_t, int32x4x3_t, int32x4x4_t, int64x1_t,
+    int64x1x2_t, int64x1x3_t, int64x1x4_t, int64x2_t, int64x2x2_t, int64x2x3_t,
+    int64x2x4_t, int8x16_t, int8x16x2_t, int8x16x3_t, int8x16x4_t, int8x8_t,
+    int8x8x2_t, int8x8x3_t, int8x8x4_t, poly16x4_t, poly16x4x2_t, poly16x4x3_t,
+    poly16x4x4_t, poly16x8_t, poly16x8x2_t, poly16x8x3_t, poly16x8x4_t,
+    poly64x1_t, poly64x1x2_t, poly64x1x3_t, poly64x1x4_t, poly64x2_t,
+    poly64x2x2_t, poly64x2x3_t, poly64x2x4_t, poly8x16_t, poly8x16x2_t,
+    poly8x16x3_t, poly8x16x4_t, poly8x8_t, poly8x8x2_t, poly8x8x3_t, poly8x8x4_t,
+    uint16x4_t, uint16x4x2_t, uint16x4x3_t, uint16x4x4_t, uint16x8_t,
+    uint16x8x2_t, uint16x8x3_t, uint16x8x4_t, uint32x2_t, uint32x2x2_t,
+    uint32x2x3_t, uint32x2x4_t, uint32x4_t, uint32x4x2_t, uint32x4x3_t,
+    uint32x4x4_t, uint64x1_t, uint64x1x2_t, uint64x1x3_t, uint64x1x4_t,
+    uint64x2_t, uint64x2x2_t, uint64x2x3_t, uint64x2x4_t, uint8x16_t,
+    uint8x16x2_t, uint8x16x3_t, uint8x16x4_t, uint8x8_t, uint8x8x2_t,
+    uint8x8x3_t, uint8x8x4_t,
+  }
+);
 
-#[cfg(target_arch = "x86")]
-unsafe impl Pod for x86::__m128i {}
-#[cfg(target_arch = "x86")]
-unsafe impl Pod for x86::__m128 {}
-#[cfg(target_arch = "x86")]
-unsafe impl Pod for x86::__m128d {}
-#[cfg(target_arch = "x86")]
-unsafe impl Pod for x86::__m256i {}
-#[cfg(target_arch = "x86")]
-unsafe impl Pod for x86::__m256 {}
-#[cfg(target_arch = "x86")]
-unsafe impl Pod for x86::__m256d {}
+impl_unsafe_marker_for_simd!(
+  #[cfg(target_arch = "x86")]
+  unsafe impl Pod for x86::{
+    __m128i, __m128, __m128d,
+    __m256i, __m256, __m256d,
+  }
+);
 
-#[cfg(target_arch = "x86_64")]
-unsafe impl Pod for x86_64::__m128i {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Pod for x86_64::__m128 {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Pod for x86_64::__m128d {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Pod for x86_64::__m256i {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Pod for x86_64::__m256 {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Pod for x86_64::__m256d {}
+impl_unsafe_marker_for_simd!(
+  #[cfg(target_arch = "x86_64")]
+  unsafe impl Pod for x86_64::{
+    __m128i, __m128, __m128d,
+    __m256i, __m256, __m256d,
+  }
+);
 
 #[cfg(feature = "nightly_portable_simd")]
+#[cfg_attr(
+  feature = "nightly_docs",
+  doc(cfg(feature = "nightly_portable_simd"))
+)]
 unsafe impl<T, const N: usize> Pod for core::simd::Simd<T, N>
 where
   T: core::simd::SimdElement + Pod,
   core::simd::LaneCount<N>: core::simd::SupportedLaneCount,
 {
 }
+
+impl_unsafe_marker_for_simd!(
+  #[cfg(all(target_arch = "x86", feature = "nightly_stdsimd"))]
+  unsafe impl Pod for x86::{
+    __m128bh, __m256bh, __m512,
+    __m512bh, __m512d, __m512i,
+  }
+);
+
+impl_unsafe_marker_for_simd!(
+  #[cfg(all(target_arch = "x86_64", feature = "nightly_stdsimd"))]
+  unsafe impl Pod for x86_64::{
+    __m128bh, __m256bh, __m512,
+    __m512bh, __m512d, __m512i,
+  }
+);
diff --git a/src/transparent.rs b/src/transparent.rs
index 0329d1c..44f2606 100644
--- a/src/transparent.rs
+++ b/src/transparent.rs
@@ -23,7 +23,9 @@ use super::*;
 ///    the only non-ZST field.
 ///
 /// 2. Any fields *other* than the `Inner` field must be trivially constructable
-///    ZSTs, for example `PhantomData`, `PhantomPinned`, etc.
+///    ZSTs, for example `PhantomData`, `PhantomPinned`, etc. (When deriving
+///    `TransparentWrapper` on a type with ZST fields, the ZST fields must be
+///    [`Zeroable`]).
 ///
 /// 3. The `Wrapper` may not impose additional alignment requirements over
 ///    `Inner`.
@@ -84,6 +86,41 @@ use super::*;
 /// let mut buf = [1, 2, 3u8];
 /// let sm = Slice::wrap_mut(&mut buf);
 /// ```
+///
+/// ## Deriving
+///
+/// When deriving, the non-wrapped fields must uphold all the normal
+/// requirements, and must also be `Zeroable`.
+#[cfg_attr(feature = "derive", doc = "```")]
+#[cfg_attr(
+  not(feature = "derive"),
+  doc = "```ignore
+// This example requires the `derive` feature."
+)]
+/// use bytemuck::TransparentWrapper;
+/// use std::marker::PhantomData;
+///
+/// #[derive(TransparentWrapper)]
+/// #[repr(transparent)]
+/// #[transparent(usize)]
+/// struct Wrapper<T: ?Sized>(usize, PhantomData<T>); // PhantomData<T> implements Zeroable for all T
+/// ```
+///
+/// Here, an error will occur, because `MyZst` does not implement `Zeroable`.
+#[cfg_attr(feature = "derive", doc = "```compile_fail")]
+#[cfg_attr(
+  not(feature = "derive"),
+  doc = "```ignore
+// This example requires the `derive` feature."
+)]
+/// use bytemuck::TransparentWrapper;
+/// struct MyZst;
+///
+/// #[derive(TransparentWrapper)]
+/// #[repr(transparent)]
+/// #[transparent(usize)]
+/// struct Wrapper(usize, MyZst); // MyZst does not implement Zeroable
+/// ```
 pub unsafe trait TransparentWrapper<Inner: ?Sized> {
   /// Convert the inner type into the wrapper type.
   #[inline]
@@ -92,6 +129,8 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
     Self: Sized,
     Inner: Sized,
   {
+    assert!(size_of::<Inner>() == size_of::<Self>());
+    assert!(align_of::<Inner>() == align_of::<Self>());
     // SAFETY: The unsafe contract requires that `Self` and `Inner` have
     // identical representations.
     unsafe { transmute!(s) }
@@ -101,8 +140,13 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
   /// type.
   #[inline]
   fn wrap_ref(s: &Inner) -> &Self {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is the best we can do to assert their metadata is the same type
+    // on stable.
+    assert!(size_of::<*const Inner>() == size_of::<*const Self>());
     unsafe {
-      assert!(size_of::<*const Inner>() == size_of::<*const Self>());
       // A pointer cast doesn't work here because rustc can't tell that
       // the vtables match (because of the `?Sized` restriction relaxation).
       // A `transmute` doesn't work because the sizes are unspecified.
@@ -119,8 +163,12 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
   /// the wrapper type.
   #[inline]
   fn wrap_mut(s: &mut Inner) -> &mut Self {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is about the best we can do on stable.
+    assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
     unsafe {
-      assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
       // A pointer cast doesn't work here because rustc can't tell that
       // the vtables match (because of the `?Sized` restriction relaxation).
       // A `transmute` doesn't work because the sizes are unspecified.
@@ -140,13 +188,11 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
     Self: Sized,
     Inner: Sized,
   {
-    unsafe {
-      assert!(size_of::<*const Inner>() == size_of::<*const Self>());
-      assert!(align_of::<*const Inner>() == align_of::<*const Self>());
-      // SAFETY: The unsafe contract requires that these two have
-      // identical representations (size and alignment).
-      core::slice::from_raw_parts(s.as_ptr() as *const Self, s.len())
-    }
+    assert!(size_of::<Inner>() == size_of::<Self>());
+    assert!(align_of::<Inner>() == align_of::<Self>());
+    // SAFETY: The unsafe contract requires that these two have
+    // identical representations (size and alignment).
+    unsafe { core::slice::from_raw_parts(s.as_ptr() as *const Self, s.len()) }
   }
 
   /// Convert a mutable slice to the inner type into a mutable slice to the
@@ -157,11 +203,11 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
     Self: Sized,
     Inner: Sized,
   {
+    assert!(size_of::<Inner>() == size_of::<Self>());
+    assert!(align_of::<Inner>() == align_of::<Self>());
+    // SAFETY: The unsafe contract requires that these two have
+    // identical representations (size and alignment).
     unsafe {
-      assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
-      assert!(align_of::<*mut Inner>() == align_of::<*mut Self>());
-      // SAFETY: The unsafe contract requires that these two have
-      // identical representations (size and alignment).
       core::slice::from_raw_parts_mut(s.as_mut_ptr() as *mut Self, s.len())
     }
   }
@@ -173,6 +219,10 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
     Self: Sized,
     Inner: Sized,
   {
+    assert!(size_of::<Inner>() == size_of::<Self>());
+    assert!(align_of::<Inner>() == align_of::<Self>());
+    // SAFETY: The unsafe contract requires that `Self` and `Inner` have
+    // identical representations.
     unsafe { transmute!(s) }
   }
 
@@ -180,8 +230,12 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
   /// type.
   #[inline]
   fn peel_ref(s: &Self) -> &Inner {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is about the best we can do on stable.
+    assert!(size_of::<*const Inner>() == size_of::<*const Self>());
     unsafe {
-      assert!(size_of::<*const Inner>() == size_of::<*const Self>());
       // A pointer cast doesn't work here because rustc can't tell that
       // the vtables match (because of the `?Sized` restriction relaxation).
       // A `transmute` doesn't work because the sizes are unspecified.
@@ -198,8 +252,12 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
   /// to the inner type.
   #[inline]
   fn peel_mut(s: &mut Self) -> &mut Inner {
+    // The unsafe contract requires that these two have
+    // identical representations, and thus identical pointer metadata.
+    // Assert that Self and Inner have the same pointer size,
+    // which is about the best we can do on stable.
+    assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
     unsafe {
-      assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
       // A pointer cast doesn't work here because rustc can't tell that
       // the vtables match (because of the `?Sized` restriction relaxation).
       // A `transmute` doesn't work because the sizes are unspecified.
@@ -219,13 +277,11 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
     Self: Sized,
     Inner: Sized,
   {
-    unsafe {
-      assert!(size_of::<*const Inner>() == size_of::<*const Self>());
-      assert!(align_of::<*const Inner>() == align_of::<*const Self>());
-      // SAFETY: The unsafe contract requires that these two have
-      // identical representations (size and alignment).
-      core::slice::from_raw_parts(s.as_ptr() as *const Inner, s.len())
-    }
+    assert!(size_of::<Inner>() == size_of::<Self>());
+    assert!(align_of::<Inner>() == align_of::<Self>());
+    // SAFETY: The unsafe contract requires that these two have
+    // identical representations (size and alignment).
+    unsafe { core::slice::from_raw_parts(s.as_ptr() as *const Inner, s.len()) }
   }
 
   /// Convert a mutable slice to the wrapped type into a mutable slice to the
@@ -236,11 +292,11 @@ pub unsafe trait TransparentWrapper<Inner: ?Sized> {
     Self: Sized,
     Inner: Sized,
   {
+    assert!(size_of::<Inner>() == size_of::<Self>());
+    assert!(align_of::<Inner>() == align_of::<Self>());
+    // SAFETY: The unsafe contract requires that these two have
+    // identical representations (size and alignment).
     unsafe {
-      assert!(size_of::<*mut Inner>() == size_of::<*mut Self>());
-      assert!(align_of::<*mut Inner>() == align_of::<*mut Self>());
-      // SAFETY: The unsafe contract requires that these two have
-      // identical representations (size and alignment).
       core::slice::from_raw_parts_mut(s.as_mut_ptr() as *mut Inner, s.len())
     }
   }
diff --git a/src/zeroable.rs b/src/zeroable.rs
index 4e94664..f7d7ed8 100644
--- a/src/zeroable.rs
+++ b/src/zeroable.rs
@@ -12,6 +12,16 @@ use super::*;
 ///   [Infallible](core::convert::Infallible)).
 /// * Your type must be allowed to be an "all zeroes" bit pattern (eg: no
 ///   [`NonNull<T>`](core::ptr::NonNull)).
+///
+/// ## Features
+///
+/// Some `impl`s are feature gated due to the MSRV policy:
+///
+/// * `MaybeUninit<T>` was not available in 1.34.0, but is available under the
+///   `zeroable_maybe_uninit` feature flag.
+/// * `Atomic*` types require Rust 1.60.0 or later to work on certain platforms,
+///   but is available under the `zeroable_atomics` feature flag.
+/// * `[T; N]` for arbitrary `N` requires the `min_const_generics` feature flag.
 pub unsafe trait Zeroable: Sized {
   /// Calls [`zeroed`](core::mem::zeroed).
   ///
@@ -41,14 +51,66 @@ unsafe impl Zeroable for i128 {}
 unsafe impl Zeroable for f32 {}
 unsafe impl Zeroable for f64 {}
 unsafe impl<T: Zeroable> Zeroable for Wrapping<T> {}
+unsafe impl<T: Zeroable> Zeroable for core::cmp::Reverse<T> {}
 
+// Note: we can't implement this for all `T: ?Sized` types because it would
+// create NULL pointers for vtables.
+// Maybe one day this could be changed to be implemented for
+// `T: ?Sized where <T as core::ptr::Pointee>::Metadata: Zeroable`.
 unsafe impl<T> Zeroable for *mut T {}
 unsafe impl<T> Zeroable for *const T {}
-unsafe impl<T: Zeroable> Zeroable for PhantomData<T> {}
+unsafe impl<T> Zeroable for *mut [T] {}
+unsafe impl<T> Zeroable for *const [T] {}
+unsafe impl Zeroable for *mut str {}
+unsafe impl Zeroable for *const str {}
+
+unsafe impl<T: ?Sized> Zeroable for PhantomData<T> {}
 unsafe impl Zeroable for PhantomPinned {}
-unsafe impl<T: Zeroable> Zeroable for ManuallyDrop<T> {}
+unsafe impl<T: Zeroable> Zeroable for core::mem::ManuallyDrop<T> {}
+unsafe impl<T: Zeroable> Zeroable for core::cell::UnsafeCell<T> {}
+unsafe impl<T: Zeroable> Zeroable for core::cell::Cell<T> {}
+
+#[cfg(feature = "zeroable_atomics")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "zeroable_atomics")))]
+mod atomic_impls {
+  use super::Zeroable;
+
+  #[cfg(target_has_atomic = "8")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicBool {}
+  #[cfg(target_has_atomic = "8")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicU8 {}
+  #[cfg(target_has_atomic = "8")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicI8 {}
+
+  #[cfg(target_has_atomic = "16")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicU16 {}
+  #[cfg(target_has_atomic = "16")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicI16 {}
+
+  #[cfg(target_has_atomic = "32")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicU32 {}
+  #[cfg(target_has_atomic = "32")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicI32 {}
+
+  #[cfg(target_has_atomic = "64")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicU64 {}
+  #[cfg(target_has_atomic = "64")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicI64 {}
+
+  #[cfg(target_has_atomic = "ptr")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicUsize {}
+  #[cfg(target_has_atomic = "ptr")]
+  unsafe impl Zeroable for core::sync::atomic::AtomicIsize {}
+
+  #[cfg(target_has_atomic = "ptr")]
+  unsafe impl<T> Zeroable for core::sync::atomic::AtomicPtr<T> {}
+}
 
 #[cfg(feature = "zeroable_maybe_uninit")]
+#[cfg_attr(
+  feature = "nightly_docs",
+  doc(cfg(feature = "zeroable_maybe_uninit"))
+)]
 unsafe impl<T> Zeroable for core::mem::MaybeUninit<T> {}
 
 unsafe impl<A: Zeroable> Zeroable for (A,) {}
@@ -97,6 +159,7 @@ unsafe impl<
 }
 
 #[cfg(feature = "min_const_generics")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "min_const_generics")))]
 unsafe impl<T, const N: usize> Zeroable for [T; N] where T: Zeroable {}
 
 #[cfg(not(feature = "min_const_generics"))]
@@ -106,248 +169,77 @@ impl_unsafe_marker_for_array!(
   512, 1024, 2048, 4096
 );
 
-#[cfg(all(target_arch = "wasm32", feature = "wasm_simd"))]
-unsafe impl Zeroable for wasm32::v128 {}
+impl_unsafe_marker_for_simd!(
+  #[cfg(all(target_arch = "wasm32", feature = "wasm_simd"))]
+  unsafe impl Zeroable for wasm32::{v128}
+);
 
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float32x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float32x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float32x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float32x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float32x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float32x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float32x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float32x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float64x1_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float64x1x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float64x1x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float64x1x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float64x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float64x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float64x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::float64x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int16x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int16x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int16x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int16x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int16x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int16x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int16x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int32x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int32x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int32x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int32x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int32x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int32x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int32x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int32x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int64x1_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int64x1x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int64x1x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int64x1x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int64x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int64x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int64x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int64x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int8x16_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int8x16x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int8x16x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int8x16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int8x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int8x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int8x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::int8x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly16x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly16x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly16x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly16x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly16x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly16x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly16x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly64x1_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly64x1x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly64x1x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly64x1x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly64x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly64x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly64x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly64x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly8x16_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly8x16x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly8x16x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly8x16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly8x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly8x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly8x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::poly8x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint16x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint16x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint16x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint16x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint16x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint16x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint16x8x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint32x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint32x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint32x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint32x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint32x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint32x4x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint32x4x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint32x4x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint64x1_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint64x1x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint64x1x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint64x1x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint64x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint64x2x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint64x2x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint64x2x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint8x16_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint8x16x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint8x16x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint8x16x4_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint8x8_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint8x8x2_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint8x8x3_t {}
-#[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
-unsafe impl Zeroable for aarch64::uint8x8x4_t {}
+impl_unsafe_marker_for_simd!(
+  #[cfg(all(target_arch = "aarch64", feature = "aarch64_simd"))]
+  unsafe impl Zeroable for aarch64::{
+    float32x2_t, float32x2x2_t, float32x2x3_t, float32x2x4_t, float32x4_t,
+    float32x4x2_t, float32x4x3_t, float32x4x4_t, float64x1_t, float64x1x2_t,
+    float64x1x3_t, float64x1x4_t, float64x2_t, float64x2x2_t, float64x2x3_t,
+    float64x2x4_t, int16x4_t, int16x4x2_t, int16x4x3_t, int16x4x4_t, int16x8_t,
+    int16x8x2_t, int16x8x3_t, int16x8x4_t, int32x2_t, int32x2x2_t, int32x2x3_t,
+    int32x2x4_t, int32x4_t, int32x4x2_t, int32x4x3_t, int32x4x4_t, int64x1_t,
+    int64x1x2_t, int64x1x3_t, int64x1x4_t, int64x2_t, int64x2x2_t, int64x2x3_t,
+    int64x2x4_t, int8x16_t, int8x16x2_t, int8x16x3_t, int8x16x4_t, int8x8_t,
+    int8x8x2_t, int8x8x3_t, int8x8x4_t, poly16x4_t, poly16x4x2_t, poly16x4x3_t,
+    poly16x4x4_t, poly16x8_t, poly16x8x2_t, poly16x8x3_t, poly16x8x4_t,
+    poly64x1_t, poly64x1x2_t, poly64x1x3_t, poly64x1x4_t, poly64x2_t,
+    poly64x2x2_t, poly64x2x3_t, poly64x2x4_t, poly8x16_t, poly8x16x2_t,
+    poly8x16x3_t, poly8x16x4_t, poly8x8_t, poly8x8x2_t, poly8x8x3_t, poly8x8x4_t,
+    uint16x4_t, uint16x4x2_t, uint16x4x3_t, uint16x4x4_t, uint16x8_t,
+    uint16x8x2_t, uint16x8x3_t, uint16x8x4_t, uint32x2_t, uint32x2x2_t,
+    uint32x2x3_t, uint32x2x4_t, uint32x4_t, uint32x4x2_t, uint32x4x3_t,
+    uint32x4x4_t, uint64x1_t, uint64x1x2_t, uint64x1x3_t, uint64x1x4_t,
+    uint64x2_t, uint64x2x2_t, uint64x2x3_t, uint64x2x4_t, uint8x16_t,
+    uint8x16x2_t, uint8x16x3_t, uint8x16x4_t, uint8x8_t, uint8x8x2_t,
+    uint8x8x3_t, uint8x8x4_t,
+  }
+);
 
-#[cfg(target_arch = "x86")]
-unsafe impl Zeroable for x86::__m128i {}
-#[cfg(target_arch = "x86")]
-unsafe impl Zeroable for x86::__m128 {}
-#[cfg(target_arch = "x86")]
-unsafe impl Zeroable for x86::__m128d {}
-#[cfg(target_arch = "x86")]
-unsafe impl Zeroable for x86::__m256i {}
-#[cfg(target_arch = "x86")]
-unsafe impl Zeroable for x86::__m256 {}
-#[cfg(target_arch = "x86")]
-unsafe impl Zeroable for x86::__m256d {}
+impl_unsafe_marker_for_simd!(
+  #[cfg(target_arch = "x86")]
+  unsafe impl Zeroable for x86::{
+    __m128i, __m128, __m128d,
+    __m256i, __m256, __m256d,
+  }
+);
 
-#[cfg(target_arch = "x86_64")]
-unsafe impl Zeroable for x86_64::__m128i {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Zeroable for x86_64::__m128 {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Zeroable for x86_64::__m128d {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Zeroable for x86_64::__m256i {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Zeroable for x86_64::__m256 {}
-#[cfg(target_arch = "x86_64")]
-unsafe impl Zeroable for x86_64::__m256d {}
+impl_unsafe_marker_for_simd!(
+  #[cfg(target_arch = "x86_64")]
+    unsafe impl Zeroable for x86_64::{
+        __m128i, __m128, __m128d,
+        __m256i, __m256, __m256d,
+    }
+);
 
 #[cfg(feature = "nightly_portable_simd")]
+#[cfg_attr(
+  feature = "nightly_docs",
+  doc(cfg(feature = "nightly_portable_simd"))
+)]
 unsafe impl<T, const N: usize> Zeroable for core::simd::Simd<T, N>
 where
   T: core::simd::SimdElement + Zeroable,
   core::simd::LaneCount<N>: core::simd::SupportedLaneCount,
 {
 }
+
+impl_unsafe_marker_for_simd!(
+  #[cfg(all(target_arch = "x86", feature = "nightly_stdsimd"))]
+  unsafe impl Zeroable for x86::{
+    __m128bh, __m256bh, __m512,
+    __m512bh, __m512d, __m512i,
+  }
+);
+
+impl_unsafe_marker_for_simd!(
+  #[cfg(all(target_arch = "x86_64", feature = "nightly_stdsimd"))]
+  unsafe impl Zeroable for x86_64::{
+    __m128bh, __m256bh, __m512,
+    __m512bh, __m512d, __m512i,
+  }
+);
diff --git a/src/zeroable_in_option.rs b/src/zeroable_in_option.rs
index db6c083..c4cf158 100644
--- a/src/zeroable_in_option.rs
+++ b/src/zeroable_in_option.rs
@@ -25,4 +25,11 @@ unsafe impl ZeroableInOption for NonZeroU64 {}
 unsafe impl ZeroableInOption for NonZeroU128 {}
 unsafe impl ZeroableInOption for NonZeroUsize {}
 
-unsafe impl<T> ZeroableInOption for NonNull<T> {}
+// Note: this does not create NULL vtable because we get `None` anyway.
+unsafe impl<T: ?Sized> ZeroableInOption for NonNull<T> {}
+unsafe impl<T: ?Sized> ZeroableInOption for &'_ T {}
+unsafe impl<T: ?Sized> ZeroableInOption for &'_ mut T {}
+
+#[cfg(feature = "extern_crate_alloc")]
+#[cfg_attr(feature = "nightly_docs", doc(cfg(feature = "extern_crate_alloc")))]
+unsafe impl<T: ?Sized> ZeroableInOption for alloc::boxed::Box<T> {}


Test patch:
diff --git a/derive/tests/basic.rs b/derive/tests/basic.rs
index 5e05555..d3d589d 100644
--- a/derive/tests/basic.rs
+++ b/derive/tests/basic.rs
@@ -1,9 +1,10 @@
 #![allow(dead_code)]
 
 use bytemuck::{
-  AnyBitPattern, Contiguous, CheckedBitPattern, NoUninit, Pod, TransparentWrapper, Zeroable,
+  AnyBitPattern, CheckedBitPattern, Contiguous, NoUninit, Pod,
+  TransparentWrapper, Zeroable, checked::CheckedCastError,
 };
-use std::marker::PhantomData;
+use std::marker::{PhantomData, PhantomPinned};
 
 #[derive(Copy, Clone, Pod, Zeroable)]
 #[repr(C)]
@@ -63,6 +64,14 @@ struct TransparentWithZeroSized<T> {
   b: PhantomData<T>,
 }
 
+struct MyZst<T>(PhantomData<T>, [u8; 0], PhantomPinned);
+unsafe impl<T> Zeroable for MyZst<T> {}
+
+#[derive(TransparentWrapper)]
+#[repr(transparent)]
+#[transparent(u16)]
+struct TransparentTupleWithCustomZeroSized<T>(u16, MyZst<T>);
+
 #[repr(u8)]
 #[derive(Clone, Copy, Contiguous)]
 enum ContiguousWithValues {
@@ -127,6 +136,16 @@ enum CheckedBitPatternEnumNonContiguous {
   E = 56,
 }
 
+#[repr(u8)]
+#[derive(Debug, Clone, Copy, NoUninit, CheckedBitPattern, PartialEq, Eq)]
+enum CheckedBitPatternEnumByteLit {
+  A = b'A',
+  B = b'B',
+  C = b'C',
+  D = b'D',
+  E = b'E',
+}
+
 #[derive(Debug, Copy, Clone, NoUninit, CheckedBitPattern, PartialEq, Eq)]
 #[repr(C)]
 struct CheckedBitPatternStruct {
@@ -136,11 +155,71 @@ struct CheckedBitPatternStruct {
 
 #[derive(Debug, Copy, Clone, AnyBitPattern, PartialEq, Eq)]
 #[repr(C)]
-struct AnyBitPatternTest {
+struct AnyBitPatternTest<A: AnyBitPattern, B: AnyBitPattern> {
+  a: A,
+  b: B,
+}
+
+#[derive(Clone, Copy, CheckedBitPattern)]
+#[repr(C, align(8))]
+struct CheckedBitPatternAlignedStruct {
   a: u16,
-  b: u16
 }
 
+#[derive(Debug, Clone, Copy, CheckedBitPattern, PartialEq, Eq)]
+#[repr(C)]
+enum CheckedBitPatternCDefaultDiscriminantEnumWithFields {
+  A(u64),
+  B { c: u64 },
+}
+
+#[derive(Debug, Clone, Copy, CheckedBitPattern, PartialEq, Eq)]
+#[repr(C, u8)]
+enum CheckedBitPatternCEnumWithFields {
+  A(u32),
+  B { c: u32 },
+}
+
+#[derive(Debug, Clone, Copy, CheckedBitPattern, PartialEq, Eq)]
+#[repr(u8)]
+enum CheckedBitPatternIntEnumWithFields {
+  A(u8),
+  B { c: u32 },
+}
+
+#[derive(Debug, Clone, Copy, CheckedBitPattern, PartialEq, Eq)]
+#[repr(transparent)]
+enum CheckedBitPatternTransparentEnumWithFields {
+  A { b: u32 },
+}
+
+// size 24, align 8.
+// first byte always the u8 discriminant, then 7 bytes of padding until the payload union since the align of the payload
+// is the greatest of the align of all the variants, which is 8 (from CheckedBitPatternCDefaultDiscriminantEnumWithFields)
+#[derive(Debug, Clone, Copy, CheckedBitPattern, PartialEq, Eq)]
+#[repr(C, u8)]
+enum CheckedBitPatternEnumNested {
+  A(CheckedBitPatternCEnumWithFields),
+  B(CheckedBitPatternCDefaultDiscriminantEnumWithFields),
+}
+
+/// ```compile_fail
+/// use bytemuck::{Pod, Zeroable};
+///
+/// #[derive(Pod, Zeroable)]
+/// #[repr(transparent)]
+/// struct TransparentSingle<T>(T);
+///
+/// struct NotPod(u32);
+///
+/// let _: u32 = bytemuck::cast(TransparentSingle(NotPod(0u32)));
+/// ```
+#[derive(
+  Debug, Copy, Clone, PartialEq, Eq, Pod, Zeroable, TransparentWrapper,
+)]
+#[repr(transparent)]
+struct NewtypeWrapperTest<T>(T);
+
 #[test]
 fn fails_cast_contiguous() {
   let can_cast = CheckedBitPatternEnumWithValues::is_valid_bit_pattern(&5);
@@ -149,7 +228,8 @@ fn fails_cast_contiguous() {
 
 #[test]
 fn passes_cast_contiguous() {
-  let res = bytemuck::checked::from_bytes::<CheckedBitPatternEnumWithValues>(&[2u8]);
+  let res =
+    bytemuck::checked::from_bytes::<CheckedBitPatternEnumWithValues>(&[2u8]);
   assert_eq!(*res, CheckedBitPatternEnumWithValues::C);
 }
 
@@ -162,10 +242,32 @@ fn fails_cast_noncontiguous() {
 #[test]
 fn passes_cast_noncontiguous() {
   let res =
-    bytemuck::checked::from_bytes::<CheckedBitPatternEnumNonContiguous>(&[56u8]);
+    bytemuck::checked::from_bytes::<CheckedBitPatternEnumNonContiguous>(&[
+      56u8,
+    ]);
   assert_eq!(*res, CheckedBitPatternEnumNonContiguous::E);
 }
 
+#[test]
+fn fails_cast_bytelit() {
+  let can_cast = CheckedBitPatternEnumByteLit::is_valid_bit_pattern(&b'a');
+  assert!(!can_cast);
+}
+
+#[test]
+fn passes_cast_bytelit() {
+  let res =
+    bytemuck::checked::cast_slice::<u8, CheckedBitPatternEnumByteLit>(b"CAB");
+  assert_eq!(
+    res,
+    [
+      CheckedBitPatternEnumByteLit::C,
+      CheckedBitPatternEnumByteLit::A,
+      CheckedBitPatternEnumByteLit::B
+    ]
+  );
+}
+
 #[test]
 fn fails_cast_struct() {
   let pod = [0u8, 24u8];
@@ -177,15 +279,174 @@ fn fails_cast_struct() {
 fn passes_cast_struct() {
   let pod = [0u8, 8u8];
   let res = bytemuck::checked::from_bytes::<CheckedBitPatternStruct>(&pod);
-  assert_eq!(*res, CheckedBitPatternStruct { a: 0, b: CheckedBitPatternEnumNonContiguous::B });
+  assert_eq!(
+    *res,
+    CheckedBitPatternStruct { a: 0, b: CheckedBitPatternEnumNonContiguous::B }
+  );
 }
 
 #[test]
 fn anybitpattern_implies_zeroable() {
-  let test = AnyBitPatternTest::zeroed();
-  assert_eq!(test, AnyBitPatternTest { a: 0, b: 0 });
+  let test = AnyBitPatternTest::<isize, usize>::zeroed();
+  assert_eq!(test, AnyBitPatternTest { a: 0isize, b: 0usize });
+}
+
+#[test]
+fn checkedbitpattern_try_pod_read_unaligned() {
+  let pod = [0u8];
+  let res = bytemuck::checked::try_pod_read_unaligned::<
+    CheckedBitPatternEnumWithValues,
+  >(&pod);
+  assert!(res.is_ok());
+
+  let pod = [5u8];
+  let res = bytemuck::checked::try_pod_read_unaligned::<
+    CheckedBitPatternEnumWithValues,
+  >(&pod);
+  assert!(res.is_err());
+}
+
+#[test]
+fn checkedbitpattern_aligned_struct() {
+  let pod = [0u8; 8];
+  bytemuck::checked::pod_read_unaligned::<CheckedBitPatternAlignedStruct>(&pod);
+}
+
+#[test]
+fn checkedbitpattern_c_default_discriminant_enum_with_fields() {
+  let pod = [
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0x55,
+    0x55, 0x55, 0x55, 0xcc,
+  ];
+  let value = bytemuck::checked::pod_read_unaligned::<
+    CheckedBitPatternCDefaultDiscriminantEnumWithFields,
+  >(&pod);
+  assert_eq!(
+    value,
+    CheckedBitPatternCDefaultDiscriminantEnumWithFields::A(0xcc555555555555cc)
+  );
+
+  let pod = [
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0x55,
+    0x55, 0x55, 0x55, 0xcc,
+  ];
+  let value = bytemuck::checked::pod_read_unaligned::<
+    CheckedBitPatternCDefaultDiscriminantEnumWithFields,
+  >(&pod);
+  assert_eq!(
+    value,
+    CheckedBitPatternCDefaultDiscriminantEnumWithFields::B {
+      c: 0xcc555555555555cc
+    }
+  );
+}
+
+#[test]
+fn checkedbitpattern_c_enum_with_fields() {
+  let pod = [0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0xcc];
+  let value = bytemuck::checked::pod_read_unaligned::<
+    CheckedBitPatternCEnumWithFields,
+  >(&pod);
+  assert_eq!(value, CheckedBitPatternCEnumWithFields::A(0xcc5555cc));
+
+  let pod = [0x01, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0xcc];
+  let value = bytemuck::checked::pod_read_unaligned::<
+    CheckedBitPatternCEnumWithFields,
+  >(&pod);
+  assert_eq!(value, CheckedBitPatternCEnumWithFields::B { c: 0xcc5555cc });
+}
+
+#[test]
+fn checkedbitpattern_int_enum_with_fields() {
+  let pod = [0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
+  let value = bytemuck::checked::pod_read_unaligned::<
+    CheckedBitPatternIntEnumWithFields,
+  >(&pod);
+  assert_eq!(value, CheckedBitPatternIntEnumWithFields::A(0x55));
+
+  let pod = [0x01, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0xcc];
+  let value = bytemuck::checked::pod_read_unaligned::<
+    CheckedBitPatternIntEnumWithFields,
+  >(&pod);
+  assert_eq!(value, CheckedBitPatternIntEnumWithFields::B { c: 0xcc5555cc });
+}
+
+#[test]
+fn checkedbitpattern_nested_enum_with_fields() {
+  // total size 24 bytes. first byte always the u8 discriminant.
+
+  #[repr(C, align(8))]
+  struct Align8Bytes([u8; 24]);
+
+  // first we'll check variantA, nested variant A
+  let pod = Align8Bytes([
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte 0 discriminant = 0 = variant A, bytes 1-7 irrelevant padding.
+    0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0xcc, // bytes 8-15 are the nested CheckedBitPatternCEnumWithFields,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes 16-23 padding
+  ]);
+  let value = bytemuck::checked::from_bytes::<
+    CheckedBitPatternEnumNested,
+  >(&pod.0);
+  assert_eq!(value, &CheckedBitPatternEnumNested::A(CheckedBitPatternCEnumWithFields::A(0xcc5555cc)));
+
+  // next we'll check invalid first discriminant fails
+  let pod = Align8Bytes([
+    0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte 0 discriminant = 2 = invalid, bytes 1-7 padding
+    0x00, 0x00, 0x00, 0x00, 0xcc, 0x55, 0x55, 0xcc, // bytes 8-15 are the nested CheckedBitPatternCEnumWithFields = A,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes 16-23 padding
+  ]);
+  let result = bytemuck::checked::try_from_bytes::<
+    CheckedBitPatternEnumNested,
+  >(&pod.0);
+  assert_eq!(result, Err(CheckedCastError::InvalidBitPattern));
+
+
+  // next we'll check variant B, nested variant B
+  let pod = Align8Bytes([
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // byte 0 discriminant = 1 = variant B, bytes 1-7 padding
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes 8-15 is C int size discriminant of CheckedBitPatternCDefaultDiscrimimantEnumWithFields, 1 (LE byte order) = variant B
+    0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xcc, // bytes 16-13 is the data contained in nested variant B
+  ]);
+  let value = bytemuck::checked::from_bytes::<
+    CheckedBitPatternEnumNested,
+  >(&pod.0);
+  assert_eq!(
+    value,
+    &CheckedBitPatternEnumNested::B(CheckedBitPatternCDefaultDiscriminantEnumWithFields::B {
+      c: 0xcc555555555555cc
+    })
+  );
+
+  // finally we'll check variant B, nested invalid discriminant
+  let pod = Align8Bytes([
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1 discriminant = variant B, bytes 1-7 padding
+    0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes 8-15 is C int size discriminant of CheckedBitPatternCDefaultDiscrimimantEnumWithFields, 0x08 is invalid
+    0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xcc, // bytes 16-13 is the data contained in nested variant B
+  ]);
+  let result = bytemuck::checked::try_from_bytes::<
+    CheckedBitPatternEnumNested,
+  >(&pod.0);
+  assert_eq!(result, Err(CheckedCastError::InvalidBitPattern));
+}
+#[test]
+fn checkedbitpattern_transparent_enum_with_fields() {
+  let pod = [0xcc, 0x55, 0x55, 0xcc];
+  let value = bytemuck::checked::pod_read_unaligned::<
+    CheckedBitPatternTransparentEnumWithFields,
+  >(&pod);
+  assert_eq!(
+    value,
+    CheckedBitPatternTransparentEnumWithFields::A { b: 0xcc5555cc }
+  );
 }
 
 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
 #[repr(C, align(16))]
 struct Issue127 {}
+
+use bytemuck as reexport_name;
+#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, bytemuck::ByteEq)]
+#[bytemuck(crate = "reexport_name")]
+#[repr(C)]
+struct Issue93 {}
+
diff --git a/tests/cast_slice_tests.rs b/tests/cast_slice_tests.rs
index 515a628..ce6dedc 100644
--- a/tests/cast_slice_tests.rs
+++ b/tests/cast_slice_tests.rs
@@ -1,3 +1,6 @@
+#![allow(clippy::unnecessary_cast)]
+#![allow(clippy::manual_slice_size_calculation)]
+
 use core::mem::size_of;
 
 use bytemuck::*;
@@ -140,7 +143,7 @@ fn test_try_from_bytes_mut() {
     Err(PodCastError::SizeMismatch)
   );
   assert_eq!(
-    try_from_bytes::<u32>(&mut bytes[1..5]),
+    try_from_bytes::<u32>(&bytes[1..5]),
     Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned)
   );
 }
diff --git a/tests/checked_tests.rs b/tests/checked_tests.rs
new file mode 100644
index 0000000..c7c5640
--- /dev/null
+++ b/tests/checked_tests.rs
@@ -0,0 +1,419 @@
+#![allow(clippy::unnecessary_cast)]
+#![allow(clippy::manual_slice_size_calculation)]
+
+use core::{
+  mem::size_of,
+  num::{NonZeroU32, NonZeroU8},
+};
+
+use bytemuck::{checked::CheckedCastError, *};
+
+#[test]
+fn test_try_cast_slice() {
+  // some align4 data
+  let nonzero_u32_slice: &[NonZeroU32] = &[
+    NonZeroU32::new(4).unwrap(),
+    NonZeroU32::new(5).unwrap(),
+    NonZeroU32::new(6).unwrap(),
+  ];
+
+  // contains bytes with invalid bitpattern for NonZeroU8
+  assert_eq!(
+    checked::try_cast_slice::<NonZeroU32, NonZeroU8>(nonzero_u32_slice),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+
+  // the same data as align1
+  let the_bytes: &[u8] = checked::try_cast_slice(nonzero_u32_slice).unwrap();
+
+  assert_eq!(
+    nonzero_u32_slice.as_ptr() as *const NonZeroU32 as usize,
+    the_bytes.as_ptr() as *const u8 as usize
+  );
+  assert_eq!(
+    nonzero_u32_slice.len() * size_of::<NonZeroU32>(),
+    the_bytes.len() * size_of::<u8>()
+  );
+
+  // by taking one byte off the front, we're definitely mis-aligned for
+  // NonZeroU32.
+  let mis_aligned_bytes = &the_bytes[1..];
+  assert_eq!(
+    checked::try_cast_slice::<u8, NonZeroU32>(mis_aligned_bytes),
+    Err(CheckedCastError::PodCastError(
+      PodCastError::TargetAlignmentGreaterAndInputNotAligned
+    ))
+  );
+
+  // by taking one byte off the end, we're aligned but would have slop bytes for
+  // NonZeroU32
+  let the_bytes_len_minus1 = the_bytes.len() - 1;
+  let slop_bytes = &the_bytes[..the_bytes_len_minus1];
+  assert_eq!(
+    checked::try_cast_slice::<u8, NonZeroU32>(slop_bytes),
+    Err(CheckedCastError::PodCastError(PodCastError::OutputSliceWouldHaveSlop))
+  );
+
+  // if we don't mess with it we can up-alignment cast
+  checked::try_cast_slice::<u8, NonZeroU32>(the_bytes).unwrap();
+}
+
+#[test]
+fn test_try_cast_slice_mut() {
+  // some align4 data
+  let u32_slice: &mut [u32] = &mut [4, 5, 6];
+
+  // contains bytes with invalid bitpattern for NonZeroU8
+  assert_eq!(
+    checked::try_cast_slice_mut::<u32, NonZeroU8>(u32_slice),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+
+  // some align4 data
+  let u32_slice: &mut [u32] = &mut [0x4444_4444, 0x5555_5555, 0x6666_6666];
+  let u32_len = u32_slice.len();
+  let u32_ptr = u32_slice.as_ptr();
+
+  // the same data as align1, nonzero bytes
+  let the_nonzero_bytes: &mut [NonZeroU8] =
+    checked::try_cast_slice_mut(u32_slice).unwrap();
+  let the_nonzero_bytes_len = the_nonzero_bytes.len();
+  let the_nonzero_bytes_ptr = the_nonzero_bytes.as_ptr();
+
+  assert_eq!(
+    u32_ptr as *const u32 as usize,
+    the_nonzero_bytes_ptr as *const NonZeroU8 as usize
+  );
+  assert_eq!(
+    u32_len * size_of::<u32>(),
+    the_nonzero_bytes_len * size_of::<NonZeroU8>()
+  );
+
+  // the same data as align1
+  let the_bytes: &mut [u8] = checked::try_cast_slice_mut(u32_slice).unwrap();
+  let the_bytes_len = the_bytes.len();
+  let the_bytes_ptr = the_bytes.as_ptr();
+
+  assert_eq!(
+    u32_ptr as *const u32 as usize,
+    the_bytes_ptr as *const u8 as usize
+  );
+  assert_eq!(
+    u32_len * size_of::<u32>(),
+    the_bytes_len * size_of::<NonZeroU8>()
+  );
+
+  // by taking one byte off the front, we're definitely mis-aligned for u32.
+  let mis_aligned_bytes = &mut the_bytes[1..];
+  assert_eq!(
+    checked::try_cast_slice_mut::<u8, NonZeroU32>(mis_aligned_bytes),
+    Err(CheckedCastError::PodCastError(
+      PodCastError::TargetAlignmentGreaterAndInputNotAligned
+    ))
+  );
+
+  // by taking one byte off the end, we're aligned but would have slop bytes for
+  // NonZeroU32
+  let the_bytes_len_minus1 = the_bytes.len() - 1;
+  let slop_bytes = &mut the_bytes[..the_bytes_len_minus1];
+  assert_eq!(
+    checked::try_cast_slice_mut::<u8, NonZeroU32>(slop_bytes),
+    Err(CheckedCastError::PodCastError(PodCastError::OutputSliceWouldHaveSlop))
+  );
+
+  // if we don't mess with it we can up-alignment cast, since there are no
+  // zeroes in the original slice
+  checked::try_cast_slice_mut::<u8, NonZeroU32>(the_bytes).unwrap();
+}
+
+#[test]
+fn test_types() {
+  let _: NonZeroU32 = checked::cast(1.0_f32);
+  let _: &mut NonZeroU32 = checked::cast_mut(&mut 1.0_f32);
+  let _: &NonZeroU32 = checked::cast_ref(&1.0_f32);
+  let _: &[NonZeroU32] = checked::cast_slice(&[1.0_f32]);
+  let _: &mut [NonZeroU32] = checked::cast_slice_mut(&mut [1.0_f32]);
+  //
+  let _: Result<NonZeroU32, CheckedCastError> = checked::try_cast(1.0_f32);
+  let _: Result<&mut NonZeroU32, CheckedCastError> =
+    checked::try_cast_mut(&mut 1.0_f32);
+  let _: Result<&NonZeroU32, CheckedCastError> =
+    checked::try_cast_ref(&1.0_f32);
+  let _: Result<&[NonZeroU32], CheckedCastError> =
+    checked::try_cast_slice(&[1.0_f32]);
+  let _: Result<&mut [NonZeroU32], CheckedCastError> =
+    checked::try_cast_slice_mut(&mut [1.0_f32]);
+}
+
+#[test]
+fn test_try_pod_read_unaligned() {
+  let u32s = [0xaabbccdd, 0x11223344_u32];
+  let bytes = bytemuck::checked::cast_slice::<u32, u8>(&u32s);
+
+  #[cfg(target_endian = "big")]
+  assert_eq!(
+    checked::try_pod_read_unaligned::<NonZeroU32>(&bytes[1..5]),
+    Ok(NonZeroU32::new(0xbbccdd11).unwrap())
+  );
+  #[cfg(target_endian = "little")]
+  assert_eq!(
+    checked::try_pod_read_unaligned::<NonZeroU32>(&bytes[1..5]),
+    Ok(NonZeroU32::new(0x44aabbcc).unwrap())
+  );
+
+  let u32s = [0; 2];
+  let bytes = bytemuck::checked::cast_slice::<u32, u8>(&u32s);
+
+  assert_eq!(
+    checked::try_pod_read_unaligned::<NonZeroU32>(&bytes[1..5]),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+}
+
+#[test]
+fn test_try_from_bytes() {
+  let nonzero_u32s = [
+    NonZeroU32::new(0xaabbccdd).unwrap(),
+    NonZeroU32::new(0x11223344).unwrap(),
+  ];
+  let bytes = bytemuck::checked::cast_slice::<NonZeroU32, u8>(&nonzero_u32s);
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[..4]),
+    Ok(&nonzero_u32s[0])
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[..5]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[..3]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[1..5]),
+    Err(CheckedCastError::PodCastError(
+      PodCastError::TargetAlignmentGreaterAndInputNotAligned
+    ))
+  );
+
+  let zero_u32s = [0, 0x11223344_u32];
+  let bytes = bytemuck::checked::cast_slice::<u32, u8>(&zero_u32s);
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[..4]),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[4..]),
+    Ok(&NonZeroU32::new(zero_u32s[1]).unwrap())
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[..5]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[..3]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[1..5]),
+    Err(CheckedCastError::PodCastError(
+      PodCastError::TargetAlignmentGreaterAndInputNotAligned
+    ))
+  );
+}
+
+#[test]
+fn test_try_from_bytes_mut() {
+  let a = 0xaabbccdd_u32;
+  let b = 0x11223344_u32;
+  let mut u32s = [a, b];
+  let bytes = bytemuck::checked::cast_slice_mut::<u32, u8>(&mut u32s);
+  assert_eq!(
+    checked::try_from_bytes_mut::<NonZeroU32>(&mut bytes[..4]),
+    Ok(&mut NonZeroU32::new(a).unwrap())
+  );
+  assert_eq!(
+    checked::try_from_bytes_mut::<NonZeroU32>(&mut bytes[4..]),
+    Ok(&mut NonZeroU32::new(b).unwrap())
+  );
+  assert_eq!(
+    checked::try_from_bytes_mut::<NonZeroU32>(&mut bytes[..5]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+  assert_eq!(
+    checked::try_from_bytes_mut::<NonZeroU32>(&mut bytes[..3]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[1..5]),
+    Err(CheckedCastError::PodCastError(
+      PodCastError::TargetAlignmentGreaterAndInputNotAligned
+    ))
+  );
+
+  let mut u32s = [0, b];
+  let bytes = bytemuck::checked::cast_slice_mut::<u32, u8>(&mut u32s);
+  assert_eq!(
+    checked::try_from_bytes_mut::<NonZeroU32>(&mut bytes[..4]),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+  assert_eq!(
+    checked::try_from_bytes_mut::<NonZeroU32>(&mut bytes[4..]),
+    Ok(&mut NonZeroU32::new(b).unwrap())
+  );
+  assert_eq!(
+    checked::try_from_bytes_mut::<NonZeroU32>(&mut bytes[..5]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+  assert_eq!(
+    checked::try_from_bytes_mut::<NonZeroU32>(&mut bytes[..3]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+  assert_eq!(
+    checked::try_from_bytes::<NonZeroU32>(&bytes[1..5]),
+    Err(CheckedCastError::PodCastError(
+      PodCastError::TargetAlignmentGreaterAndInputNotAligned
+    ))
+  );
+}
+
+#[test]
+fn test_from_bytes() {
+  let abcd = 0xaabbccdd_u32;
+  let aligned_bytes = bytemuck::bytes_of(&abcd);
+  assert_eq!(
+    checked::from_bytes::<NonZeroU32>(aligned_bytes),
+    &NonZeroU32::new(abcd).unwrap()
+  );
+  assert!(core::ptr::eq(
+    checked::from_bytes(aligned_bytes) as *const NonZeroU32 as *const u32,
+    &abcd
+  ));
+}
+
+#[test]
+fn test_from_bytes_mut() {
+  let mut a = 0xaabbccdd_u32;
+  let a_addr = &a as *const _ as usize;
+  let aligned_bytes = bytemuck::bytes_of_mut(&mut a);
+  assert_eq!(
+    *checked::from_bytes_mut::<NonZeroU32>(aligned_bytes),
+    NonZeroU32::new(0xaabbccdd).unwrap()
+  );
+  assert_eq!(
+    checked::from_bytes_mut::<NonZeroU32>(aligned_bytes) as *const NonZeroU32
+      as usize,
+    a_addr
+  );
+}
+
+// like #[should_panic], but can be a part of another test, instead of requiring
+// it to be it's own test.
+macro_rules! should_panic {
+  ($ex:expr) => {
+    assert!(
+      std::panic::catch_unwind(|| {
+        let _ = $ex;
+      })
+      .is_err(),
+      concat!("should have panicked: `", stringify!($ex), "`")
+    );
+  };
+}
+
+#[test]
+fn test_panics() {
+  should_panic!(checked::cast::<u32, NonZeroU32>(0));
+  should_panic!(checked::cast_ref::<u32, NonZeroU32>(&0));
+  should_panic!(checked::cast_mut::<u32, NonZeroU32>(&mut 0));
+  should_panic!(checked::cast_slice::<u8, NonZeroU32>(&[1u8, 2u8]));
+  should_panic!(checked::cast_slice_mut::<u8, NonZeroU32>(&mut [1u8, 2u8]));
+  should_panic!(checked::from_bytes::<NonZeroU32>(&[1u8, 2]));
+  should_panic!(checked::from_bytes::<NonZeroU32>(&[1u8, 2, 3, 4, 5]));
+  should_panic!(checked::from_bytes_mut::<NonZeroU32>(&mut [1u8, 2]));
+  should_panic!(checked::from_bytes_mut::<NonZeroU32>(&mut [1u8, 2, 3, 4, 5]));
+  // use cast_slice on some u32s to get some align>=4 bytes, so we can know
+  // we'll give from_bytes unaligned ones.
+  let aligned_bytes = bytemuck::cast_slice::<u32, u8>(&[0, 0]);
+  should_panic!(checked::from_bytes::<NonZeroU32>(aligned_bytes));
+  should_panic!(checked::from_bytes::<NonZeroU32>(&aligned_bytes[1..5]));
+  should_panic!(checked::pod_read_unaligned::<NonZeroU32>(
+    &aligned_bytes[1..5]
+  ));
+}
+
+#[test]
+fn test_char() {
+  assert_eq!(checked::try_cast::<u32, char>(0), Ok('\0'));
+  assert_eq!(checked::try_cast::<u32, char>(0xd7ff), Ok('\u{d7ff}'));
+  assert_eq!(
+    checked::try_cast::<u32, char>(0xd800),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+  assert_eq!(
+    checked::try_cast::<u32, char>(0xdfff),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+  assert_eq!(checked::try_cast::<u32, char>(0xe000), Ok('\u{e000}'));
+  assert_eq!(checked::try_cast::<u32, char>(0x10ffff), Ok('\u{10ffff}'));
+  assert_eq!(
+    checked::try_cast::<u32, char>(0x110000),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+  assert_eq!(
+    checked::try_cast::<u32, char>(-1i32 as u32),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+}
+
+#[test]
+fn test_bool() {
+  assert_eq!(checked::try_cast::<u8, bool>(0), Ok(false));
+  assert_eq!(checked::try_cast::<u8, bool>(1), Ok(true));
+  for i in 2..=255 {
+    assert_eq!(
+      checked::try_cast::<u8, bool>(i),
+      Err(CheckedCastError::InvalidBitPattern)
+    );
+  }
+
+  assert_eq!(checked::try_from_bytes::<bool>(&[1]), Ok(&true));
+  assert_eq!(
+    checked::try_from_bytes::<bool>(&[3]),
+    Err(CheckedCastError::InvalidBitPattern)
+  );
+  assert_eq!(
+    checked::try_from_bytes::<bool>(&[0, 1]),
+    Err(CheckedCastError::PodCastError(PodCastError::SizeMismatch))
+  );
+}
+
+#[test]
+fn test_all_nonzero() {
+  use core::num::*;
+  macro_rules! test_nonzero {
+    ($nonzero:ty: $primitive:ty) => {
+      assert_eq!(
+        checked::try_cast::<$primitive, $nonzero>(0),
+        Err(CheckedCastError::InvalidBitPattern)
+      );
+      assert_eq!(
+        checked::try_cast::<$primitive, $nonzero>(1),
+        Ok(<$nonzero>::new(1).unwrap())
+      );
+    };
+  }
+
+  test_nonzero!(NonZeroU8: u8);
+  test_nonzero!(NonZeroI8: i8);
+  test_nonzero!(NonZeroU16: u16);
+  test_nonzero!(NonZeroI16: i16);
+  test_nonzero!(NonZeroU32: u32);
+  test_nonzero!(NonZeroI32: i32);
+  test_nonzero!(NonZeroU64: u64);
+  test_nonzero!(NonZeroI64: i64);
+  test_nonzero!(NonZeroU128: u128);
+  test_nonzero!(NonZeroI128: i128);
+  test_nonzero!(NonZeroUsize: usize);
+  test_nonzero!(NonZeroIsize: isize);
+}
diff --git a/tests/derive.rs b/tests/derive.rs
index 2524e6d..1c6b10e 100644
--- a/tests/derive.rs
+++ b/tests/derive.rs
@@ -1,9 +1,10 @@
 #![cfg(feature = "derive")]
 #![allow(dead_code)]
 
-use bytemuck::{Pod, TransparentWrapper, Zeroable};
+use bytemuck::{ByteEq, ByteHash, Pod, TransparentWrapper, Zeroable};
+use std::marker::PhantomData;
 
-#[derive(Copy, Clone, Pod, Zeroable)]
+#[derive(Copy, Clone, Pod, Zeroable, ByteEq, ByteHash)]
 #[repr(C)]
 struct Test {
   a: u16,
@@ -23,3 +24,54 @@ struct TransparentWithZeroSized {
   a: u16,
   b: (),
 }
+
+#[derive(TransparentWrapper)]
+#[repr(transparent)]
+struct TransparentWithGeneric<T: ?Sized> {
+  a: T,
+}
+
+/// Ensuring that no additional bounds are emitted.
+/// See https://github.com/Lokathor/bytemuck/issues/145
+fn test_generic<T>(x: T) -> TransparentWithGeneric<T> {
+  TransparentWithGeneric::wrap(x)
+}
+
+#[derive(TransparentWrapper)]
+#[repr(transparent)]
+#[transparent(T)]
+struct TransparentWithGenericAndZeroSized<T: ?Sized> {
+  a: (),
+  b: T,
+}
+
+/// Ensuring that no additional bounds are emitted.
+/// See https://github.com/Lokathor/bytemuck/issues/145
+fn test_generic_with_zst<T>(x: T) -> TransparentWithGenericAndZeroSized<T> {
+  TransparentWithGenericAndZeroSized::wrap(x)
+}
+
+#[derive(TransparentWrapper)]
+#[repr(transparent)]
+struct TransparentUnsized {
+  a: dyn std::fmt::Debug,
+}
+
+type DynDebug = dyn std::fmt::Debug;
+
+#[derive(TransparentWrapper)]
+#[repr(transparent)]
+#[transparent(DynDebug)]
+struct TransparentUnsizedWithZeroSized {
+  a: (),
+  b: DynDebug,
+}
+
+#[derive(TransparentWrapper)]
+#[repr(transparent)]
+#[transparent(DynDebug)]
+struct TransparentUnsizedWithGenericZeroSizeds<T: ?Sized, U: ?Sized> {
+  a: PhantomData<T>,
+  b: PhantomData<U>,
+  c: DynDebug,
+}
diff --git a/tests/doc_tests.rs b/tests/doc_tests.rs
index e5a80db..92b1b5c 100644
--- a/tests/doc_tests.rs
+++ b/tests/doc_tests.rs
@@ -1,3 +1,6 @@
+#![allow(clippy::disallowed_names)]
+#![allow(dead_code)]
+
 //! Cargo miri doesn't run doctests yet, so we duplicate these here. It's
 //! probably not that important to sweat keeping these perfectly up to date, but
 //! we should try to catch the cases where the primary tests are doctests.
@@ -103,7 +106,7 @@ fn test_offsetof_nonpod() {
   // Note: offsets are in bytes.
   let as_bytes = &foo as *const _ as *const u8;
 
-  // we're using wrapping_offset here becasue it's not worth
+  // We're using wrapping_offset here because it's not worth
   // the unsafe block, but it would be valid to use `add` instead,
   // as it cannot overflow.
   assert_eq!(
diff --git a/tests/offset_of_tests.rs b/tests/offset_of_tests.rs
index b957514..4cad5d4 100644
--- a/tests/offset_of_tests.rs
+++ b/tests/offset_of_tests.rs
@@ -1,3 +1,4 @@
+#![allow(clippy::disallowed_names)]
 use bytemuck::{offset_of, Zeroable};
 
 #[test]
diff --git a/tests/std_tests.rs b/tests/std_tests.rs
index c8ea9e3..b881e5e 100644
--- a/tests/std_tests.rs
+++ b/tests/std_tests.rs
@@ -1,7 +1,10 @@
+#![allow(clippy::uninlined_format_args)]
+#![allow(unused_imports)]
 //! The integration tests seem to always have `std` linked, so things that would
 //! depend on that can go here.
 
 use bytemuck::*;
+use core::num::NonZeroU8;
 
 #[test]
 fn test_transparent_vtabled() {
@@ -43,3 +46,62 @@ fn test_zero_sized_box_alloc() {
   unsafe impl Zeroable for Empty {}
   let _: Box<Empty> = try_zeroed_box().unwrap();
 }
+
+#[test]
+#[cfg(feature = "extern_crate_alloc")]
+fn test_try_from_box_bytes() {
+  // Different layout: target alignment is greater than source alignment.
+  assert_eq!(
+    try_from_box_bytes::<u32>(Box::new([0u8; 4]).into()).map_err(|(x, _)| x),
+    Err(PodCastError::AlignmentMismatch)
+  );
+
+  // Different layout: target alignment is less than source alignment.
+  assert_eq!(
+    try_from_box_bytes::<u32>(Box::new(0u64).into()).map_err(|(x, _)| x),
+    Err(PodCastError::AlignmentMismatch)
+  );
+
+  // Different layout: target size is greater than source size.
+  assert_eq!(
+    try_from_box_bytes::<[u32; 2]>(Box::new(0u32).into()).map_err(|(x, _)| x),
+    Err(PodCastError::SizeMismatch)
+  );
+
+  // Different layout: target size is less than source size.
+  assert_eq!(
+    try_from_box_bytes::<u32>(Box::new([0u32; 2]).into()).map_err(|(x, _)| x),
+    Err(PodCastError::SizeMismatch)
+  );
+
+  // Round trip: alignment is equal to size.
+  assert_eq!(*from_box_bytes::<u32>(Box::new(1000u32).into()), 1000u32);
+
+  // Round trip: alignment is divider of size.
+  assert_eq!(&*from_box_bytes::<[u8; 5]>(Box::new(*b"hello").into()), b"hello");
+
+  // It's ok for T to have uninitialized bytes.
+  #[cfg(feature = "derive")]
+  {
+    #[derive(Debug, Copy, Clone, PartialEq, Eq, AnyBitPattern)]
+    struct Foo(u8, u16);
+    assert_eq!(
+      *from_box_bytes::<Foo>(Box::new([0xc5c5u16; 2]).into()),
+      Foo(0xc5u8, 0xc5c5u16)
+    );
+  }
+}
+
+#[test]
+#[cfg(feature = "extern_crate_alloc")]
+fn test_box_bytes_of() {
+  assert_eq!(&*box_bytes_of(Box::new(*b"hello")), b"hello");
+
+  #[cfg(target_endian = "big")]
+  assert_eq!(&*box_bytes_of(Box::new(0x12345678)), b"\x12\x34\x56\x78");
+  #[cfg(target_endian = "little")]
+  assert_eq!(&*box_bytes_of(Box::new(0x12345678)), b"\x78\x56\x34\x12");
+
+  // It's ok for T to have invalid bit patterns.
+  assert_eq!(&*box_bytes_of(Box::new(NonZeroU8::new(0xc5))), b"\xc5");
+}
diff --git a/tests/transparent.rs b/tests/transparent.rs
index 78d4fe1..be26c21 100644
--- a/tests/transparent.rs
+++ b/tests/transparent.rs
@@ -77,7 +77,7 @@ fn test_transparent_wrapper() {
   #[cfg(feature = "extern_crate_alloc")]
   {
     use bytemuck::allocation::TransparentWrapperAlloc;
-    use std::{rc::Rc, sync::Arc};
+    use std::rc::Rc;
 
     let a: Vec<Foreign> = vec![Foreign::default(); 2];
 
@@ -101,11 +101,16 @@ fn test_transparent_wrapper() {
     let i: Rc<Foreign> = Wrapper::peel_rc(h);
     assert_eq!(&*i, &0);
 
-    let j: Arc<Foreign> = Arc::new(Foreign::default());
+    #[cfg(target_has_atomic = "ptr")]
+    {
+      use std::sync::Arc;
 
-    let k: Arc<Wrapper> = Wrapper::wrap_arc(j);
-    assert_eq!(&*k, &0);
-    let l: Arc<Foreign> = Wrapper::peel_arc(k);
-    assert_eq!(&*l, &0);
+      let j: Arc<Foreign> = Arc::new(Foreign::default());
+
+      let k: Arc<Wrapper> = Wrapper::wrap_arc(j);
+      assert_eq!(&*k, &0);
+      let l: Arc<Foreign> = Wrapper::peel_arc(k);
+      assert_eq!(&*l, &0);
+    }
   }
 }