Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ default = ["std"]
std = ["alloc"]
alloc = []
arrayvec = ["dep:arrayvec"]
bytemuck = ["dep:bytemuck"]
bytes = ["dep:bytes"]

[dev-dependencies]
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"

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`].

Expand Down
14 changes: 14 additions & 0 deletions src/combinators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
125 changes: 125 additions & 0 deletions src/combinators/pod.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
value: T,
}

impl<T> Pod<T> {
/// 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<T> From<T> for Pod<T> {
#[inline]
fn from(value: T) -> Self {
Self::new(value)
}
}

impl<T> AsRef<T> for Pod<T> {
#[inline]
fn as_ref(&self) -> &T {
&self.value
}
}
impl<T> Borrow<T> for Pod<T> {
#[inline]
fn borrow(&self) -> &T {
&self.value
}
}

impl<E, T> Encodable<E> for Pod<T>
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<u32> = 42u32.into();
assert_eq!(pod.into_inner(), 42u32);
}
}