From f96b311bf9070f3c00d08830a7bb7df23acf46ca Mon Sep 17 00:00:00 2001 From: williamwutq Date: Thu, 3 Sep 2026 23:19:08 -0700 Subject: [PATCH 1/8] feat: Add bytemuck crate support for Pod types --- CHANGELOG.md | 4 ++ Cargo.toml | 2 + README.md | 2 + src/combinators/mod.rs | 14 +++++ src/combinators/pod.rs | 136 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+) create mode 100644 src/combinators/pod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b2d18..886c297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `bytemuck` feature with a `Pod` combinator that encodes any `bytemuck::Pod` value as its raw byte representation + ## [1.0.1](https://github.com/Altair-Bueno/encode/compare/v1.0.0...v1.0.1) - 2026-09-02 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 66f9eff..462d680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ std = ["alloc"] alloc = [] arrayvec = ["dep:arrayvec"] bytes = ["dep:bytes"] +bytemuck = ["dep:bytemuck"] [dev-dependencies] rstest = "0.18" @@ -28,6 +29,7 @@ rstest = "0.18" [dependencies] arrayvec = { version = "0.7.6", optional = true, default-features = false } bytes = { version = "1.10.1", optional = true, default-features = false } +bytemuck = { version = "1.25.2", optional = true, default-features = false } pastey = "0.2" [[example]] diff --git a/README.md b/README.md index f5deb0e..05d1dbf 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ crate. [`StrEncoder`] for [`arrayvec::ArrayString`]. - `bytes`: Implements [`Encodable`] and [`ByteEncoder`] for [`bytes::BytesMut`]. Implements [`Encodable`] for [`bytes::Bytes`]. +- `bytemuck`: Adds the [`Pod`](combinators::Pod) combinator, which implements + [`Encodable`] for any [`bytemuck::Pod`]. ## FAQs diff --git a/src/combinators/mod.rs b/src/combinators/mod.rs index ed8aea6..c86a3e5 100644 --- a/src/combinators/mod.rs +++ b/src/combinators/mod.rs @@ -81,6 +81,16 @@ These types are supported when the `bytes` feature is enabled. | [`Bytes`] | Encodes a `Bytes` object as a contiguous sequence of bytes | | [`BytesMut`] | Encodes a `BytesMut` object as a contiguous sequence of bytes | +" +)] +#[cfg_attr( + feature = "bytemuck", + doc = r"## Bytemuck Combinators (requires the `bytemuck` feature) +These types are supported when the `bytemuck` feature is enabled. +| Type | Description | +|------|-------------| +| [`Pod`] | Encodes any [`bytemuck::Pod`] value as its raw byte representation | + " )] mod be; @@ -89,6 +99,8 @@ mod from_error; mod iter; mod le; mod length_prefix; +#[cfg(feature = "bytemuck")] +mod pod; mod separated; pub use be::BE; @@ -97,4 +109,6 @@ pub use from_error::FromError; pub use iter::Iter; pub use le::LE; pub use length_prefix::LengthPrefix; +#[cfg(feature = "bytemuck")] +pub use pod::Pod; pub use separated::Separated; diff --git a/src/combinators/pod.rs b/src/combinators/pod.rs new file mode 100644 index 0000000..a73db40 --- /dev/null +++ b/src/combinators/pod.rs @@ -0,0 +1,136 @@ +use core::borrow::Borrow; +use core::ops::Deref; + +use crate::ByteEncoder; +use crate::Encodable; + +/// Encodes any [`bytemuck::Pod`] value as its raw byte representation. +/// +/// This is useful for encoding plain-old-data types, such as `#[repr(C)]` +/// structs, without having to manually implement [`Encodable`] for them. +/// +/// Note that [`Pod`] encodes the value using the machine's native byte order. +/// Wrap the value with [`LE`](super::LE) or [`BE`](super::BE) beforehand if a +/// specific byte order is required. +/// +/// # Examples +/// +/// ```rust +/// # #[cfg(feature = "alloc")] { +/// use encode::Encodable; +/// use encode::combinators::Pod; +/// +/// let mut buf = Vec::new(); +/// Pod::new(42u32).encode(&mut buf).unwrap(); +/// assert_eq!(buf.len(), 4); +/// # } +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct Pod { + value: T, +} + +impl Pod { + /// Creates a new [`Pod`] combinator. + #[inline] + #[must_use] + pub const fn new(value: T) -> Self { + Self { value } + } + /// Consumes the [`Pod`] combinator and returns the inner value. + #[inline] + #[must_use] + pub fn into_inner(self) -> T { + self.value + } +} + +impl From for Pod { + #[inline] + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl AsRef for Pod { + #[inline] + fn as_ref(&self) -> &T { + &self.value + } +} +impl Borrow for Pod { + #[inline] + fn borrow(&self) -> &T { + &self.value + } +} +impl Deref for Pod { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl Encodable for Pod +where + E: ByteEncoder, + T: bytemuck::Pod, +{ + type Error = E::Error; + + #[inline] + fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { + bytemuck::bytes_of(&self.value).encode(encoder) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const BUF_SIZE: usize = 32; + + #[test] + fn assert_that_a_pod_value_can_be_encoded() { + let expected = 0x2A_u32.to_ne_bytes(); + let mut buf = [0u8; BUF_SIZE]; + let mut encoder = &mut buf as &mut [u8]; + Pod::new(0x2A_u32).encode(&mut encoder).unwrap(); + let written = BUF_SIZE - encoder.len(); + assert_eq!(&buf[..written], &expected); + } + + #[test] + fn assert_that_pod_into_inner_returns_the_value() { + let pod = Pod::new(42u32); + assert_eq!(pod.into_inner(), 42u32); + } + + #[test] + fn assert_that_pod_deref_works() { + let pod = Pod::new(42u32); + assert_eq!(*pod, 42u32); + } + + #[test] + fn assert_that_pod_as_ref_works() { + let pod = Pod::new(42u32); + assert_eq!(pod.as_ref(), &42u32); + } + + #[test] + fn assert_that_pod_borrow_works() { + let pod = Pod::new(42u32); + let borrowed: &u32 = pod.borrow(); + assert_eq!(*borrowed, 42u32); + } + + #[test] + fn assert_that_from_value_into_pod_works() { + let pod: Pod = 42u32.into(); + assert_eq!(pod.into_inner(), 42u32); + } +} From d59d5e728ba3269d55ecfd42c8220890b9c1ff50 Mon Sep 17 00:00:00 2001 From: williamwutq Date: Fri, 4 Sep 2026 14:08:55 -0700 Subject: [PATCH 2/8] docs: Remove manual CHANGELOG --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 886c297..54b2d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Added - -- Add `bytemuck` feature with a `Pod` combinator that encodes any `bytemuck::Pod` value as its raw byte representation - ## [1.0.1](https://github.com/Altair-Bueno/encode/compare/v1.0.0...v1.0.1) - 2026-09-02 ### Fixed From fcdd64811b5a746f1c4548f3eb7c42c60724b72c Mon Sep 17 00:00:00 2001 From: williamwutq Date: Fri, 4 Sep 2026 14:10:08 -0700 Subject: [PATCH 3/8] fix: Remove Deref for Pod --- src/combinators/pod.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/combinators/pod.rs b/src/combinators/pod.rs index a73db40..3b139fb 100644 --- a/src/combinators/pod.rs +++ b/src/combinators/pod.rs @@ -1,5 +1,4 @@ use core::borrow::Borrow; -use core::ops::Deref; use crate::ByteEncoder; use crate::Encodable; @@ -65,14 +64,6 @@ impl Borrow for Pod { &self.value } } -impl Deref for Pod { - type Target = T; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.value - } -} impl Encodable for Pod where @@ -109,12 +100,6 @@ mod tests { assert_eq!(pod.into_inner(), 42u32); } - #[test] - fn assert_that_pod_deref_works() { - let pod = Pod::new(42u32); - assert_eq!(*pod, 42u32); - } - #[test] fn assert_that_pod_as_ref_works() { let pod = Pod::new(42u32); From 7f250bedb99ba68ebd57898c283bd1090dedd0ea Mon Sep 17 00:00:00 2001 From: williamwutq Date: Fri, 4 Sep 2026 14:19:22 -0700 Subject: [PATCH 4/8] fix: Encodable for Pod wrapped in LE or BE --- src/combinators/be.rs | 24 ++++++++++++++++++++++++ src/combinators/le.rs | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/combinators/be.rs b/src/combinators/be.rs index 40c513a..e6e989c 100644 --- a/src/combinators/be.rs +++ b/src/combinators/be.rs @@ -138,6 +138,30 @@ impl_try_from_be_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); impl_encodeable_be_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64); impl_encodeable_be_for_nonzero_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); +/// Encodes the raw representation of a [`Pod`] value in big-endian order. +/// +/// The byte order applies to the representation as a whole: the bytes are +/// emitted in reverse whenever the host order differs. See [`Pod`] for when +/// that is meaningful. +#[cfg(feature = "bytemuck")] +impl Encodable for BE> +where + E: ByteEncoder, + T: bytemuck::Pod, +{ + type Error = E::Error; + + #[inline] + fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { + let bytes = bytemuck::bytes_of(self.num.as_ref()); + if cfg!(target_endian = "big") { + encoder.put_slice(bytes) + } else { + bytes.iter().rev().try_for_each(|&b| encoder.put_byte(b)) + } + } +} + #[cfg(test)] mod tests { use core::borrow::Borrow; diff --git a/src/combinators/le.rs b/src/combinators/le.rs index d6b8e9e..24313c9 100644 --- a/src/combinators/le.rs +++ b/src/combinators/le.rs @@ -138,6 +138,30 @@ impl_try_from_le_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); impl_encodeable_le_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64); impl_encodeable_le_for_nonzero_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); +/// Encodes the raw representation of a [`Pod`] value in little-endian order. +/// +/// The byte order applies to the representation as a whole: the bytes are +/// emitted in reverse whenever the host order differs. See [`Pod`] for when +/// that is meaningful. +#[cfg(feature = "bytemuck")] +impl Encodable for LE> +where + E: ByteEncoder, + T: bytemuck::Pod, +{ + type Error = E::Error; + + #[inline] + fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { + let bytes = bytemuck::bytes_of(self.num.as_ref()); + if cfg!(target_endian = "little") { + encoder.put_slice(bytes) + } else { + bytes.iter().rev().try_for_each(|&b| encoder.put_byte(b)) + } + } +} + #[cfg(test)] mod tests { use core::borrow::Borrow; From 452c3a3ee4dbd3e1b0368785a5bb664aed45175f Mon Sep 17 00:00:00 2001 From: williamwutq Date: Fri, 4 Sep 2026 14:37:23 -0700 Subject: [PATCH 5/8] fix: Only allow LE on LE, BE on BE --- README.md | 2 +- src/combinators/be.rs | 16 ++++-------- src/combinators/le.rs | 16 ++++-------- src/combinators/mod.rs | 2 +- src/combinators/pod.rs | 58 +++++++++++++++++++++++------------------- 5 files changed, 44 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 05d1dbf..8c8f7c9 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ crate. - `bytes`: Implements [`Encodable`] and [`ByteEncoder`] for [`bytes::BytesMut`]. Implements [`Encodable`] for [`bytes::Bytes`]. - `bytemuck`: Adds the [`Pod`](combinators::Pod) combinator, which implements - [`Encodable`] for any [`bytemuck::Pod`]. + [`Encodable`] for any [`bytemuck::Pod`] wrapped in the native endianness type. ## FAQs diff --git a/src/combinators/be.rs b/src/combinators/be.rs index e6e989c..fe8b666 100644 --- a/src/combinators/be.rs +++ b/src/combinators/be.rs @@ -138,12 +138,11 @@ impl_try_from_be_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); impl_encodeable_be_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64); impl_encodeable_be_for_nonzero_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); -/// Encodes the raw representation of a [`Pod`] value in big-endian order. +/// Encodes the raw representation of a [`Pod`](super::Pod) value. /// -/// The byte order applies to the representation as a whole: the bytes are -/// emitted in reverse whenever the host order differs. See [`Pod`] for when -/// that is meaningful. -#[cfg(feature = "bytemuck")] +/// Big-endian targets only: a raw representation cannot be +/// meaningfully reversed, so there is no such impl on little-endian targets. +#[cfg(all(feature = "bytemuck", target_endian = "big"))] impl Encodable for BE> where E: ByteEncoder, @@ -153,12 +152,7 @@ where #[inline] fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { - let bytes = bytemuck::bytes_of(self.num.as_ref()); - if cfg!(target_endian = "big") { - encoder.put_slice(bytes) - } else { - bytes.iter().rev().try_for_each(|&b| encoder.put_byte(b)) - } + encoder.put_slice(bytemuck::bytes_of(self.num.as_ref())) } } diff --git a/src/combinators/le.rs b/src/combinators/le.rs index 24313c9..9e08717 100644 --- a/src/combinators/le.rs +++ b/src/combinators/le.rs @@ -138,12 +138,11 @@ impl_try_from_le_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); impl_encodeable_le_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64); impl_encodeable_le_for_nonzero_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); -/// Encodes the raw representation of a [`Pod`] value in little-endian order. +/// Encodes the raw representation of a [`Pod`](super::Pod) value. /// -/// The byte order applies to the representation as a whole: the bytes are -/// emitted in reverse whenever the host order differs. See [`Pod`] for when -/// that is meaningful. -#[cfg(feature = "bytemuck")] +/// Little-endian targets only: a raw representation cannot be +/// meaningfully reversed, so there is no such impl on big-endian targets. +#[cfg(all(feature = "bytemuck", target_endian = "little"))] impl Encodable for LE> where E: ByteEncoder, @@ -153,12 +152,7 @@ where #[inline] fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { - let bytes = bytemuck::bytes_of(self.num.as_ref()); - if cfg!(target_endian = "little") { - encoder.put_slice(bytes) - } else { - bytes.iter().rev().try_for_each(|&b| encoder.put_byte(b)) - } + encoder.put_slice(bytemuck::bytes_of(self.num.as_ref())) } } diff --git a/src/combinators/mod.rs b/src/combinators/mod.rs index c86a3e5..8e1b10a 100644 --- a/src/combinators/mod.rs +++ b/src/combinators/mod.rs @@ -89,7 +89,7 @@ These types are supported when the `bytes` feature is enabled. These types are supported when the `bytemuck` feature is enabled. | Type | Description | |------|-------------| -| [`Pod`] | Encodes any [`bytemuck::Pod`] value as its raw byte representation | +| [`Pod`] | Encodes any [`bytemuck::Pod`] value as its raw byte representation, wrapped in [`LE`] or [`BE`] | " )] diff --git a/src/combinators/pod.rs b/src/combinators/pod.rs index 3b139fb..4b17bbe 100644 --- a/src/combinators/pod.rs +++ b/src/combinators/pod.rs @@ -1,26 +1,24 @@ use core::borrow::Borrow; -use crate::ByteEncoder; -use crate::Encodable; - /// Encodes any [`bytemuck::Pod`] value as its raw byte representation. /// /// This is useful for encoding plain-old-data types, such as `#[repr(C)]` -/// structs, without having to manually implement [`Encodable`] for them. +/// structs, without having to manually implement [`Encodable`](crate::Encodable) +/// for them. /// -/// Note that [`Pod`] encodes the value using the machine's native byte order. -/// Wrap the value with [`LE`](super::LE) or [`BE`](super::BE) beforehand if a -/// specific byte order is required. +/// Must be wrapped in [`LE`](super::LE) or [`BE`](super::BE), and only the one +/// matching the target's byte order exists as a raw representation cannot be +/// meaningfully reversed. /// /// # Examples /// /// ```rust -/// # #[cfg(feature = "alloc")] { +/// # #[cfg(all(feature = "alloc", target_endian = "little"))] { /// use encode::Encodable; -/// use encode::combinators::Pod; +/// use encode::combinators::{LE, Pod}; /// /// let mut buf = Vec::new(); -/// Pod::new(42u32).encode(&mut buf).unwrap(); +/// LE::new(Pod::new(42u32)).encode(&mut buf).unwrap(); /// assert_eq!(buf.len(), 4); /// # } /// ``` @@ -65,31 +63,39 @@ impl Borrow for Pod { } } -impl Encodable for Pod -where - E: ByteEncoder, - T: bytemuck::Pod, -{ - type Error = E::Error; - - #[inline] - fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { - bytemuck::bytes_of(&self.value).encode(encoder) - } -} - #[cfg(test)] mod tests { use super::*; + use crate::Encodable; const BUF_SIZE: usize = 32; + #[cfg(target_endian = "little")] #[test] - fn assert_that_a_pod_value_can_be_encoded() { - let expected = 0x2A_u32.to_ne_bytes(); + fn assert_that_a_le_wrapped_pod_value_is_encoded_in_little_endian_order() { + use crate::combinators::LE; + + let expected = 0x0102_0304_u32.to_le_bytes(); + let mut buf = [0u8; BUF_SIZE]; + let mut encoder = &mut buf as &mut [u8]; + LE::new(Pod::new(0x0102_0304_u32)) + .encode(&mut encoder) + .unwrap(); + let written = BUF_SIZE - encoder.len(); + assert_eq!(&buf[..written], &expected); + } + + #[cfg(target_endian = "big")] + #[test] + fn assert_that_a_be_wrapped_pod_value_is_encoded_in_big_endian_order() { + use crate::combinators::BE; + + let expected = 0x0102_0304_u32.to_be_bytes(); let mut buf = [0u8; BUF_SIZE]; let mut encoder = &mut buf as &mut [u8]; - Pod::new(0x2A_u32).encode(&mut encoder).unwrap(); + BE::new(Pod::new(0x0102_0304_u32)) + .encode(&mut encoder) + .unwrap(); let written = BUF_SIZE - encoder.len(); assert_eq!(&buf[..written], &expected); } From 15413e8cf1d2105d37e5bb30cb64b3dc0905219b Mon Sep 17 00:00:00 2001 From: williamwutq Date: Sun, 6 Sep 2026 17:49:16 -0700 Subject: [PATCH 6/8] fix: use native endian order --- README.md | 2 +- src/combinators/be.rs | 18 --------------- src/combinators/le.rs | 18 --------------- src/combinators/mod.rs | 2 +- src/combinators/pod.rs | 52 +++++++++++++++++++----------------------- 5 files changed, 25 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 8c8f7c9..8bf8829 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ crate. - `bytes`: Implements [`Encodable`] and [`ByteEncoder`] for [`bytes::BytesMut`]. Implements [`Encodable`] for [`bytes::Bytes`]. - `bytemuck`: Adds the [`Pod`](combinators::Pod) combinator, which implements - [`Encodable`] for any [`bytemuck::Pod`] wrapped in the native endianness type. + [`Encodable`] for any [`bytemuck::Pod`] type, in the platform's native byte order. ## FAQs diff --git a/src/combinators/be.rs b/src/combinators/be.rs index fe8b666..40c513a 100644 --- a/src/combinators/be.rs +++ b/src/combinators/be.rs @@ -138,24 +138,6 @@ impl_try_from_be_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); impl_encodeable_be_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64); impl_encodeable_be_for_nonzero_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); -/// Encodes the raw representation of a [`Pod`](super::Pod) value. -/// -/// Big-endian targets only: a raw representation cannot be -/// meaningfully reversed, so there is no such impl on little-endian targets. -#[cfg(all(feature = "bytemuck", target_endian = "big"))] -impl Encodable for BE> -where - E: ByteEncoder, - T: bytemuck::Pod, -{ - type Error = E::Error; - - #[inline] - fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { - encoder.put_slice(bytemuck::bytes_of(self.num.as_ref())) - } -} - #[cfg(test)] mod tests { use core::borrow::Borrow; diff --git a/src/combinators/le.rs b/src/combinators/le.rs index 9e08717..d6b8e9e 100644 --- a/src/combinators/le.rs +++ b/src/combinators/le.rs @@ -138,24 +138,6 @@ impl_try_from_le_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); impl_encodeable_le_for_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64); impl_encodeable_le_for_nonzero_num!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); -/// Encodes the raw representation of a [`Pod`](super::Pod) value. -/// -/// Little-endian targets only: a raw representation cannot be -/// meaningfully reversed, so there is no such impl on big-endian targets. -#[cfg(all(feature = "bytemuck", target_endian = "little"))] -impl Encodable for LE> -where - E: ByteEncoder, - T: bytemuck::Pod, -{ - type Error = E::Error; - - #[inline] - fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { - encoder.put_slice(bytemuck::bytes_of(self.num.as_ref())) - } -} - #[cfg(test)] mod tests { use core::borrow::Borrow; diff --git a/src/combinators/mod.rs b/src/combinators/mod.rs index 8e1b10a..7e1b704 100644 --- a/src/combinators/mod.rs +++ b/src/combinators/mod.rs @@ -89,7 +89,7 @@ These types are supported when the `bytes` feature is enabled. These types are supported when the `bytemuck` feature is enabled. | Type | Description | |------|-------------| -| [`Pod`] | Encodes any [`bytemuck::Pod`] value as its raw byte representation, wrapped in [`LE`] or [`BE`] | +| [`Pod`] | Encodes any [`bytemuck::Pod`] value as its raw byte representation, in native byte order | " )] diff --git a/src/combinators/pod.rs b/src/combinators/pod.rs index 4b17bbe..84cedc2 100644 --- a/src/combinators/pod.rs +++ b/src/combinators/pod.rs @@ -1,24 +1,25 @@ use core::borrow::Borrow; +use crate::ByteEncoder; +use crate::Encodable; + /// Encodes any [`bytemuck::Pod`] value as its raw byte representation. /// /// This is useful for encoding plain-old-data types, such as `#[repr(C)]` /// structs, without having to manually implement [`Encodable`](crate::Encodable) /// for them. /// -/// Must be wrapped in [`LE`](super::LE) or [`BE`](super::BE), and only the one -/// matching the target's byte order exists as a raw representation cannot be -/// meaningfully reversed. +/// The value is written in the target platform's native byte order. /// /// # Examples /// /// ```rust -/// # #[cfg(all(feature = "alloc", target_endian = "little"))] { +/// # #[cfg(feature = "alloc")] { /// use encode::Encodable; -/// use encode::combinators::{LE, Pod}; +/// use encode::combinators::Pod; /// /// let mut buf = Vec::new(); -/// LE::new(Pod::new(42u32)).encode(&mut buf).unwrap(); +/// Pod::new(42u32).encode(&mut buf).unwrap(); /// assert_eq!(buf.len(), 4); /// # } /// ``` @@ -63,6 +64,19 @@ impl Borrow for Pod { } } +impl Encodable for Pod +where + E: ByteEncoder, + T: bytemuck::Pod, +{ + type Error = E::Error; + + #[inline] + fn encode(&self, encoder: &mut E) -> Result<(), Self::Error> { + encoder.put_slice(bytemuck::bytes_of(&self.value)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -70,32 +84,12 @@ mod tests { const BUF_SIZE: usize = 32; - #[cfg(target_endian = "little")] #[test] - fn assert_that_a_le_wrapped_pod_value_is_encoded_in_little_endian_order() { - use crate::combinators::LE; - - let expected = 0x0102_0304_u32.to_le_bytes(); - let mut buf = [0u8; BUF_SIZE]; - let mut encoder = &mut buf as &mut [u8]; - LE::new(Pod::new(0x0102_0304_u32)) - .encode(&mut encoder) - .unwrap(); - let written = BUF_SIZE - encoder.len(); - assert_eq!(&buf[..written], &expected); - } - - #[cfg(target_endian = "big")] - #[test] - fn assert_that_a_be_wrapped_pod_value_is_encoded_in_big_endian_order() { - use crate::combinators::BE; - - let expected = 0x0102_0304_u32.to_be_bytes(); + fn assert_that_a_pod_value_is_encoded_in_native_byte_order() { + let expected = 0x0102_0304_u32.to_ne_bytes(); let mut buf = [0u8; BUF_SIZE]; let mut encoder = &mut buf as &mut [u8]; - BE::new(Pod::new(0x0102_0304_u32)) - .encode(&mut encoder) - .unwrap(); + Pod::new(0x0102_0304_u32).encode(&mut encoder).unwrap(); let written = BUF_SIZE - encoder.len(); assert_eq!(&buf[..written], &expected); } From 79df91699a217c5dfc8666ee6976493f8a3762eb Mon Sep 17 00:00:00 2001 From: Altair-Bueno <67512202+Altair-Bueno@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:11:20 +0200 Subject: [PATCH 7/8] style: wrap README bytemuck entry and sort feature entries The bytemuck bullet exceeded prettier's `proseWrap: always` width, failing the prettier-fmt job. Also keeps the feature and dependency entries alphabetically sorted alongside `arrayvec` and `bytes`. Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.toml | 4 ++-- README.md | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 462d680..355989e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,16 +20,16 @@ default = ["std"] std = ["alloc"] alloc = [] arrayvec = ["dep:arrayvec"] -bytes = ["dep:bytes"] bytemuck = ["dep:bytemuck"] +bytes = ["dep:bytes"] [dev-dependencies] rstest = "0.18" [dependencies] arrayvec = { version = "0.7.6", optional = true, default-features = false } -bytes = { version = "1.10.1", optional = true, default-features = false } bytemuck = { version = "1.25.2", optional = true, default-features = false } +bytes = { version = "1.10.1", optional = true, default-features = false } pastey = "0.2" [[example]] diff --git a/README.md b/README.md index 8bf8829..797506a 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,11 @@ crate. - `arrayvec`: Implements [`Encodable`] and [`ByteEncoder`] for [`arrayvec::ArrayVec`] and [`arrayvec::ArrayString`]. Implements [`StrEncoder`] for [`arrayvec::ArrayString`]. +- `bytemuck`: Adds the [`Pod`](combinators::Pod) combinator, which implements + [`Encodable`] for any [`bytemuck::Pod`] type, in the platform's native byte + order. - `bytes`: Implements [`Encodable`] and [`ByteEncoder`] for [`bytes::BytesMut`]. Implements [`Encodable`] for [`bytes::Bytes`]. -- `bytemuck`: Adds the [`Pod`](combinators::Pod) combinator, which implements - [`Encodable`] for any [`bytemuck::Pod`] type, in the platform's native byte order. ## FAQs From f86a88ce10bd9705aba09b1b93585d288faf26cb Mon Sep 17 00:00:00 2001 From: Altair-Bueno <67512202+Altair-Bueno@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:11:20 +0200 Subject: [PATCH 8/8] docs: warn that Pod encodes in non-portable native endianness Point users at the LE/BE combinators when a stable wire format is needed. Co-Authored-By: Claude Opus 5 (1M context) --- src/combinators/mod.rs | 2 +- src/combinators/pod.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/combinators/mod.rs b/src/combinators/mod.rs index 7e1b704..2115310 100644 --- a/src/combinators/mod.rs +++ b/src/combinators/mod.rs @@ -89,7 +89,7 @@ These types are supported when the `bytes` feature is enabled. These types are supported when the `bytemuck` feature is enabled. | Type | Description | |------|-------------| -| [`Pod`] | Encodes any [`bytemuck::Pod`] value as its raw byte representation, in native byte order | +| [`Pod`] | Encodes any [`bytemuck::Pod`] value as its raw byte representation, in the platform's native byte order | " )] diff --git a/src/combinators/pod.rs b/src/combinators/pod.rs index 84cedc2..e89aede 100644 --- a/src/combinators/pod.rs +++ b/src/combinators/pod.rs @@ -9,7 +9,11 @@ use crate::Encodable; /// structs, without having to manually implement [`Encodable`](crate::Encodable) /// for them. /// -/// The value is written in the target platform's native byte order. +/// The value is written in the target platform's native byte order, so the +/// output is **not portable** across platforms with different endianness. For +/// a stable wire format, encode the individual fields with the +/// [`LE`](crate::combinators::LE) or [`BE`](crate::combinators::BE) +/// combinators instead. /// /// # Examples ///