Skip to main content

solana_zero_copy/
unaligned.rs

1//! Unaligned primitive wrapper types for zero-copy data structures.
2//! These wrappers preserve a stable byte layout for primitive values without
3//! introducing alignment requirements from the native integer types.
4
5#[cfg(feature = "bytemuck")]
6use bytemuck_derive::{Pod, Zeroable};
7#[cfg(feature = "serde")]
8use serde_derive::{Deserialize, Serialize};
9#[cfg(feature = "wincode")]
10use wincode::{SchemaRead, SchemaWrite};
11#[cfg(feature = "borsh")]
12use {
13    alloc::string::ToString,
14    borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
15};
16
17/// The standard `bool` is not naturally zero-copy, define an unaligned replacement.
18///
19/// Any nonzero value is interpreted as `true`; only `0` is interpreted as `false`.
20#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
21#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23#[cfg_attr(feature = "serde", serde(from = "bool", into = "bool"))]
24#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
25#[derive(Clone, Copy, Debug, Default, PartialEq)]
26#[repr(transparent)]
27pub struct Bool(pub u8);
28impl Bool {
29    pub const fn from_bool(b: bool) -> Self {
30        Self(if b { 1 } else { 0 })
31    }
32}
33
34impl From<bool> for Bool {
35    fn from(b: bool) -> Self {
36        Self::from_bool(b)
37    }
38}
39
40impl From<&bool> for Bool {
41    fn from(b: &bool) -> Self {
42        Self(if *b { 1 } else { 0 })
43    }
44}
45
46impl From<&Bool> for bool {
47    fn from(b: &Bool) -> Self {
48        b.0 != 0
49    }
50}
51
52impl From<Bool> for bool {
53    fn from(b: Bool) -> Self {
54        b.0 != 0
55    }
56}
57
58/// Simple macro for implementing conversion functions between unaligned
59/// integers and standard integers.
60///
61/// The standard integer types can cause alignment issues when placed in a
62/// `bytemuck::Pod`, so these replacements are usable in all bytemuck `Pod`
63/// types.
64#[macro_export]
65macro_rules! impl_int_conversion {
66    ($P:ty, $I:ty) => {
67        impl $P {
68            pub const fn from_primitive(n: $I) -> Self {
69                Self(n.to_le_bytes())
70            }
71        }
72        impl From<$I> for $P {
73            fn from(n: $I) -> Self {
74                Self::from_primitive(n)
75            }
76        }
77        impl From<$P> for $I {
78            fn from(unaligned: $P) -> Self {
79                Self::from_le_bytes(unaligned.0)
80            }
81        }
82    };
83}
84
85/// Unaligned `u16` type that can be embedded in bytemuck `Pod` types.
86#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
87#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
88#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
89#[cfg_attr(feature = "serde", serde(from = "u16", into = "u16"))]
90#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
91#[derive(Clone, Copy, Debug, Default, PartialEq)]
92#[repr(transparent)]
93pub struct U16(pub [u8; 2]);
94impl_int_conversion!(U16, u16);
95
96/// Unaligned `i16` type that can be embedded in bytemuck `Pod` types.
97#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
98#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
99#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
100#[cfg_attr(feature = "serde", serde(from = "i16", into = "i16"))]
101#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
102#[derive(Clone, Copy, Debug, Default, PartialEq)]
103#[repr(transparent)]
104pub struct I16(pub [u8; 2]);
105impl_int_conversion!(I16, i16);
106
107/// Unaligned `u32` type that can be embedded in bytemuck `Pod` types.
108#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
109#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
110#[cfg_attr(
111    feature = "borsh",
112    derive(BorshDeserialize, BorshSerialize, BorshSchema)
113)]
114#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
115#[cfg_attr(feature = "serde", serde(from = "u32", into = "u32"))]
116#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
117#[derive(Clone, Copy, Debug, Default, PartialEq)]
118#[repr(transparent)]
119pub struct U32(pub [u8; 4]);
120impl_int_conversion!(U32, u32);
121
122/// Unaligned `u64` type that can be embedded in bytemuck `Pod` types.
123#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
124#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
125#[cfg_attr(
126    feature = "borsh",
127    derive(BorshDeserialize, BorshSerialize, BorshSchema)
128)]
129#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
130#[cfg_attr(feature = "serde", serde(from = "u64", into = "u64"))]
131#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
132#[derive(Clone, Copy, Debug, Default, PartialEq)]
133#[repr(transparent)]
134pub struct U64(pub [u8; 8]);
135impl_int_conversion!(U64, u64);
136
137/// Unaligned `i64` type that can be embedded in bytemuck `Pod` types.
138#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
139#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
140#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
141#[cfg_attr(feature = "serde", serde(from = "i64", into = "i64"))]
142#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
143#[derive(Clone, Copy, Debug, Default, PartialEq)]
144#[repr(transparent)]
145pub struct I64([u8; 8]);
146impl_int_conversion!(I64, i64);
147
148/// Unaligned `u128` type that can be embedded in bytemuck `Pod` types.
149#[cfg(not(target_arch = "bpf"))]
150#[cfg_attr(feature = "wincode", derive(SchemaRead, SchemaWrite))]
151#[cfg_attr(feature = "wincode", wincode(assert_zero_copy))]
152#[cfg_attr(
153    feature = "borsh",
154    derive(BorshDeserialize, BorshSerialize, BorshSchema)
155)]
156#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
157#[cfg_attr(feature = "serde", serde(from = "u128", into = "u128"))]
158#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
159#[derive(Clone, Copy, Debug, Default, PartialEq)]
160#[repr(transparent)]
161pub struct U128(pub [u8; 16]);
162#[cfg(not(target_arch = "bpf"))]
163impl_int_conversion!(U128, u128);
164
165/// Implements the `TryFrom<usize>` and `From<T> for usize` conversions for an
166/// unaligned integer type.
167macro_rules! impl_usize_conversion {
168    ($UnalignedType:ty, $PrimitiveType:ty) => {
169        impl TryFrom<usize> for $UnalignedType {
170            type Error = core::num::TryFromIntError;
171
172            fn try_from(val: usize) -> Result<Self, Self::Error> {
173                let primitive_val = <$PrimitiveType>::try_from(val)?;
174                Ok(primitive_val.into())
175            }
176        }
177
178        impl From<$UnalignedType> for usize {
179            fn from(unaligned_val: $UnalignedType) -> Self {
180                let primitive_val = <$PrimitiveType>::from(unaligned_val);
181                Self::try_from(primitive_val)
182                    .expect("value out of range for usize on this platform")
183            }
184        }
185    };
186}
187
188impl_usize_conversion!(U16, u16);
189impl_usize_conversion!(U32, u32);
190impl_usize_conversion!(U64, u64);
191#[cfg(not(target_arch = "bpf"))]
192impl_usize_conversion!(U128, u128);
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[cfg(feature = "bytemuck")]
199    #[test]
200    fn test_bool() {
201        assert!(bytemuck::try_from_bytes::<Bool>(&[]).is_err());
202        assert!(bytemuck::try_from_bytes::<Bool>(&[0, 0]).is_err());
203
204        for i in 0..=u8::MAX {
205            assert_eq!(i != 0, bool::from(*bytemuck::from_bytes::<Bool>(&[i])));
206        }
207    }
208
209    #[cfg(feature = "serde")]
210    #[test]
211    fn test_bool_serde() {
212        let unaligned_false: Bool = false.into();
213        let unaligned_true: Bool = true.into();
214
215        let serialized_false = serde_json::to_string(&unaligned_false).unwrap();
216        let serialized_true = serde_json::to_string(&unaligned_true).unwrap();
217        assert_eq!(&serialized_false, "false");
218        assert_eq!(&serialized_true, "true");
219
220        let deserialized_false = serde_json::from_str::<Bool>(&serialized_false).unwrap();
221        let deserialized_true = serde_json::from_str::<Bool>(&serialized_true).unwrap();
222        assert_eq!(unaligned_false, deserialized_false);
223        assert_eq!(unaligned_true, deserialized_true);
224    }
225
226    #[cfg(feature = "bytemuck")]
227    #[test]
228    fn test_u16() {
229        assert!(bytemuck::try_from_bytes::<U16>(&[]).is_err());
230        assert_eq!(1u16, u16::from(*bytemuck::from_bytes::<U16>(&[1, 0])));
231    }
232
233    #[cfg(feature = "serde")]
234    #[test]
235    fn test_u16_serde() {
236        let unaligned_u16: U16 = u16::MAX.into();
237
238        let serialized = serde_json::to_string(&unaligned_u16).unwrap();
239        assert_eq!(&serialized, "65535");
240
241        let deserialized = serde_json::from_str::<U16>(&serialized).unwrap();
242        assert_eq!(unaligned_u16, deserialized);
243    }
244
245    #[cfg(feature = "bytemuck")]
246    #[test]
247    fn test_i16() {
248        assert!(bytemuck::try_from_bytes::<I16>(&[]).is_err());
249        assert_eq!(-1i16, i16::from(*bytemuck::from_bytes::<I16>(&[255, 255])));
250    }
251
252    #[cfg(feature = "serde")]
253    #[test]
254    fn test_i16_serde() {
255        let unaligned_i16: I16 = i16::MAX.into();
256        let serialized = serde_json::to_string(&unaligned_i16).unwrap();
257        assert_eq!(&serialized, "32767");
258
259        let deserialized = serde_json::from_str::<I16>(&serialized).unwrap();
260        assert_eq!(unaligned_i16, deserialized);
261    }
262
263    #[cfg(feature = "bytemuck")]
264    #[test]
265    fn test_u64() {
266        assert!(bytemuck::try_from_bytes::<U64>(&[]).is_err());
267        assert_eq!(
268            1u64,
269            u64::from(*bytemuck::from_bytes::<U64>(&[1, 0, 0, 0, 0, 0, 0, 0]))
270        );
271    }
272
273    #[cfg(feature = "serde")]
274    #[test]
275    fn test_u64_serde() {
276        let unaligned_u64: U64 = u64::MAX.into();
277
278        let serialized = serde_json::to_string(&unaligned_u64).unwrap();
279        assert_eq!(&serialized, "18446744073709551615");
280
281        let deserialized = serde_json::from_str::<U64>(&serialized).unwrap();
282        assert_eq!(unaligned_u64, deserialized);
283    }
284
285    #[cfg(feature = "bytemuck")]
286    #[test]
287    fn test_i64() {
288        assert!(bytemuck::try_from_bytes::<I64>(&[]).is_err());
289        assert_eq!(
290            -1i64,
291            i64::from(*bytemuck::from_bytes::<I64>(&[
292                255, 255, 255, 255, 255, 255, 255, 255
293            ]))
294        );
295    }
296
297    #[cfg(feature = "serde")]
298    #[test]
299    fn test_i64_serde() {
300        let unaligned_i64: I64 = i64::MAX.into();
301
302        let serialized = serde_json::to_string(&unaligned_i64).unwrap();
303        assert_eq!(&serialized, "9223372036854775807");
304
305        let deserialized = serde_json::from_str::<I64>(&serialized).unwrap();
306        assert_eq!(unaligned_i64, deserialized);
307    }
308
309    #[cfg(not(target_arch = "bpf"))]
310    #[cfg(feature = "bytemuck")]
311    #[test]
312    fn test_u128() {
313        assert!(bytemuck::try_from_bytes::<U128>(&[]).is_err());
314        assert_eq!(
315            1u128,
316            u128::from(*bytemuck::from_bytes::<U128>(&[
317                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
318            ]))
319        );
320    }
321
322    #[cfg(not(target_arch = "bpf"))]
323    #[cfg(feature = "serde")]
324    #[test]
325    fn test_u128_serde() {
326        let unaligned_u128: U128 = u128::MAX.into();
327
328        let serialized = serde_json::to_string(&unaligned_u128).unwrap();
329        assert_eq!(&serialized, "340282366920938463463374607431768211455");
330
331        let deserialized = serde_json::from_str::<U128>(&serialized).unwrap();
332        assert_eq!(unaligned_u128, deserialized);
333    }
334
335    macro_rules! test_usize_roundtrip {
336        ($test_name:ident, $UnalignedType:ty, $max:expr) => {
337            #[test]
338            fn $test_name() {
339                // zero
340                let unaligned = <$UnalignedType>::try_from(0usize).unwrap();
341                assert_eq!(usize::from(unaligned), 0);
342
343                // mid-range
344                let unaligned = <$UnalignedType>::try_from(42usize).unwrap();
345                assert_eq!(usize::from(unaligned), 42);
346
347                // max
348                let max = $max as usize;
349                let unaligned = <$UnalignedType>::try_from(max).unwrap();
350                assert_eq!(usize::from(unaligned), max);
351            }
352        };
353    }
354
355    test_usize_roundtrip!(test_usize_roundtrip_u16, U16, u16::MAX);
356    test_usize_roundtrip!(test_usize_roundtrip_u32, U32, u32::MAX);
357    test_usize_roundtrip!(test_usize_roundtrip_u64, U64, u64::MAX);
358    #[cfg(not(target_arch = "bpf"))]
359    test_usize_roundtrip!(test_usize_roundtrip_u128, U128, u128::MAX);
360
361    #[cfg(feature = "wincode")]
362    mod wincode_tests {
363        use {super::*, test_case::test_case};
364
365        #[test_case(Bool::from_bool(true))]
366        #[test_case(Bool::from_bool(false))]
367        #[test_case(U16::from_primitive(u16::MAX))]
368        #[test_case(I16::from_primitive(i16::MIN))]
369        #[test_case(U32::from_primitive(u32::MAX))]
370        #[test_case(U64::from_primitive(u64::MAX))]
371        #[test_case(I64::from_primitive(i64::MIN))]
372        #[cfg(not(target_arch = "bpf"))]
373        #[test_case(U128::from_primitive(u128::MAX))]
374        fn wincode_roundtrip<
375            T: PartialEq
376                + core::fmt::Debug
377                + wincode::ZeroCopy
378                + for<'de> wincode::SchemaRead<'de, wincode::config::DefaultConfig, Dst = T>
379                + wincode::SchemaWrite<wincode::config::DefaultConfig, Src = T>,
380        >(
381            value: T,
382        ) {
383            let size = wincode::serialized_size(&value).unwrap() as usize;
384            let mut bytes = [0u8; 32];
385            assert!(size <= bytes.len());
386            wincode::serialize_into(&mut bytes[..size], &value).unwrap();
387
388            let deserialized: T = wincode::deserialize(&bytes[..size]).unwrap();
389            assert_eq!(value, deserialized);
390
391            let zero_copy_ref = <T as wincode::ZeroCopy>::from_bytes(&bytes[..size]).unwrap();
392            assert_eq!(&value, zero_copy_ref);
393        }
394    }
395}