Skip to main content

primitives/utils/codec/
prims.rs

1//! [`InPlaceCodec`] for primitive scalar types used as `HeapArray` elements.
2//!
3//! All encodings are fixed-width little-endian, hence architecture-independent. `usize` is encoded
4//! as a fixed 8 bytes (not the platform word size) so the format is portable across 32/64-bit
5//! targets; decoding validates the value fits into the local `usize`.
6
7use std::mem::MaybeUninit;
8
9use super::InPlaceCodec;
10use crate::errors::PrimitiveError;
11
12macro_rules! impl_inplace_codec_le {
13    ($($ty:ty),* $(,)?) => {$(
14        // SAFETY: `write_le_bytes` writes all `size_of::<$ty>()` little-endian bytes (initializing
15        // every one); every bit pattern of that width is a valid `$ty`, so `read_le_bytes` is a
16        // total, unbiased inverse. Architecture-independent by construction (fixed LE).
17        unsafe impl InPlaceCodec for $ty {
18            const ENCODED_SIZE: usize = std::mem::size_of::<$ty>();
19
20            fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]) {
21                for (slot, &b) in out.iter_mut().zip(self.to_le_bytes().iter()) {
22                    slot.write(b);
23                }
24            }
25
26            fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
27                let bytes = bytes.try_into().map_err(|_| {
28                    PrimitiveError::InvalidSize(Self::ENCODED_SIZE, bytes.len())
29                })?;
30                Ok(<$ty>::from_le_bytes(bytes))
31            }
32        }
33    )*};
34}
35
36impl_inplace_codec_le!(u8, u16, u32, u64);
37
38// SAFETY: `bool` encodes as a single canonical byte (`0`/`1`); `write_le_bytes` initializes it and
39// `read_le_bytes` rejects any other value, making the round-trip unbiased.
40unsafe impl InPlaceCodec for bool {
41    const ENCODED_SIZE: usize = 1;
42
43    fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]) {
44        out[0].write(*self as u8);
45    }
46
47    fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
48        match bytes[0] {
49            0 => Ok(false),
50            1 => Ok(true),
51            other => Err(PrimitiveError::DeserializationFailed(format!(
52                "non-canonical bool byte: {other}"
53            ))),
54        }
55    }
56}
57
58// SAFETY: `usize` is encoded as a fixed 8-byte little-endian value (architecture-independent);
59// `write_le_bytes` initializes all 8 bytes and `read_le_bytes` rejects values that don't fit the
60// local `usize` (relevant on 32-bit targets), so the round-trip is unbiased where it succeeds.
61unsafe impl InPlaceCodec for usize {
62    const ENCODED_SIZE: usize = 8;
63
64    fn write_le_bytes(&self, out: &mut [MaybeUninit<u8>]) {
65        for (slot, &b) in out.iter_mut().zip((*self as u64).to_le_bytes().iter()) {
66            slot.write(b);
67        }
68    }
69
70    fn read_le_bytes(bytes: &[u8]) -> Result<Self, PrimitiveError> {
71        let bytes = bytes
72            .try_into()
73            .map_err(|_| PrimitiveError::InvalidSize(Self::ENCODED_SIZE, bytes.len()))?;
74        let value = u64::from_le_bytes(bytes);
75        usize::try_from(value).map_err(|_| {
76            PrimitiveError::DeserializationFailed(format!("usize out of range: {value}"))
77        })
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::InPlaceCodec;
84
85    fn roundtrip<T: InPlaceCodec + PartialEq + std::fmt::Debug>(v: T) {
86        let bytes = v.to_inplace_bytes();
87        assert_eq!(bytes.len(), T::ENCODED_SIZE);
88        assert_eq!(T::from_inplace_bytes(&bytes).unwrap(), v);
89    }
90
91    #[test]
92    fn prim_inplace_roundtrips() {
93        roundtrip(true);
94        roundtrip(false);
95        roundtrip(0xABu8);
96        roundtrip(0x1234u16);
97        roundtrip(0xDEAD_BEEFu32);
98        roundtrip(0x0123_4567_89AB_CDEFu64);
99        roundtrip(1234567usize);
100    }
101
102    #[test]
103    fn bool_rejects_non_canonical() {
104        assert!(bool::from_inplace_bytes(&[2]).is_err());
105    }
106}