diff --git a/Cargo.toml b/Cargo.toml index 66f9eff..355989e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ default = ["std"] std = ["alloc"] alloc = [] arrayvec = ["dep:arrayvec"] +bytemuck = ["dep:bytemuck"] bytes = ["dep:bytes"] [dev-dependencies] @@ -27,6 +28,7 @@ rstest = "0.18" [dependencies] arrayvec = { version = "0.7.6", 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" diff --git a/README.md b/README.md index f5deb0e..797506a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,9 @@ 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`]. diff --git a/src/combinators/mod.rs b/src/combinators/mod.rs index ed8aea6..2115310 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, in the platform's native byte order | + " )] 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..e89aede --- /dev/null +++ b/src/combinators/pod.rs @@ -0,0 +1,125 @@ +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. +/// +/// 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 +/// +/// ```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 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::*; + use crate::Encodable; + + const BUF_SIZE: usize = 32; + + #[test] + 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]; + Pod::new(0x0102_0304_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_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); + } +}