endian_cast/
lib.rs

1//! This crate provides a convenient [`EndianCast`] type and a trait [`Endianness`] for
2//! converting between little-endian and big-endian byte representations of various data types,
3//! including a way to cast to the opposite endianness.
4//!
5//! ## [`EndianCast`]
6//!
7//! The [`EndianCast`] type is a wrapper around a value of type `T` allowing you to convert it
8//! into little-endian, big-endian, native-endian, or opposite-endian byte arrays, even within
9//! const contexts. When instantiating [`EndianCast`], you must specify the size of the type
10//! `T` as a const generic. Providing an incorrect size will result in a panic at compile time.
11//!
12//! When the system endianness matches the requested endianness, the conversion is essentially
13//! a no-op, and the bytes are returned as-is. When the system endianness does not match the
14//! requested endianness, the bytes are reversed.
15//!
16//! The [`EndianCast`] interface is provided as a way to offer the same functionality as the
17//! [`Endianness`] trait within const contexts, as the trait methods cannot be used in const
18//! contexts.
19//!
20//! ### Example
21//! ```
22//! use endian_cast::EndianCast;
23//!
24//! let x = 0x12345678u32;
25//! let le_bytes = EndianCast::<4, u32>(x).le_bytes();
26//! let be_bytes = EndianCast::<4, u32>(x).be_bytes();
27//! let op_bytes = EndianCast::<4, u32>(x).op_bytes();
28//! let ne_bytes = *EndianCast::<4, u32>(x).ne_bytes();
29//!
30//! assert_ne!(le_bytes, be_bytes);
31//! assert_ne!(ne_bytes, op_bytes);
32//! assert_eq!(le_bytes, [0x78, 0x56, 0x34, 0x12]);
33//! assert_eq!(be_bytes, [0x12, 0x34, 0x56, 0x78]);
34//!
35//! // usable in const contexts
36//! const LE: [u8; 4] = EndianCast::<4, u32>(0x12345678).le_bytes();
37//! ```
38//!
39//! ## [`Endianness`]
40//!
41//! The [`Endianness`] trait provides methods for converting to and from the byte
42//! representation of the type implementing the trait. It is automatically implemented for all
43//! primitive types and arrays of bytes and can easily be implemented on arbitrary types that
44//! implement [`Sized`] and [`Copy`] by manually implementing the trait. As with
45//! [`EndianCast`], you must specify the size of the type as a const generic, and providing an
46//! incorrect size will result in a panic at compile time.
47//!
48//! When the system endianness matches the requested endianness, the conversion is essentially
49//! a no-op, and the bytes are returned as-is. When the system endianness does not match the
50//! requested endianness, the bytes are reversed.
51//!
52//! ### Example
53//! ```
54//! use endian_cast::Endianness;
55//!
56//! let x = 0x12345678u32;
57//! let le_bytes = x.le_bytes();
58//! let be_bytes = x.be_bytes();
59//! let op_bytes = x.op_bytes();
60//! let ne_bytes = *x.ne_bytes();
61//! assert_ne!(le_bytes, be_bytes);
62//! assert_ne!(ne_bytes, op_bytes);
63//! assert_eq!(le_bytes, [0x78, 0x56, 0x34, 0x12].into());
64//! assert_eq!(be_bytes, [0x12, 0x34, 0x56, 0x78].into());
65//! ```
66
67#![no_std]
68
69use generic_array::{ArrayLength, GenericArray};
70use typenum::{U1, U2, U4, U8, U16, Unsigned};
71
72/// The [`EndianCast`] type is a wrapper around a value of type `T` allowing you to convert it
73/// into little-endian, big-endian, native-endian, or opposite-endian byte arrays, even within
74/// const contexts. When instantiating [`EndianCast`], you must specify the size of the type
75/// `T` as a const generic. Providing an incorrect size will result in a panic at compile time.
76///
77/// When the system endianness matches the requested endianness, the conversion is essentially
78/// a no-op, and the bytes are returned as-is. When the system endianness does not match the
79/// requested endianness, the bytes are reversed.
80///
81/// The [`EndianCast`] interface is provided as a way to offer the same functionality as the
82/// [`Endianness`] trait within const contexts, as the trait methods cannot be used in const
83/// contexts.
84///
85/// ## Example
86/// ```
87/// use endian_cast::EndianCast;
88///
89/// let x = 0x12345678u32;
90/// let le_bytes = EndianCast::<4, u32>(x).le_bytes();
91/// let be_bytes = EndianCast::<4, u32>(x).be_bytes();
92/// let op_bytes = EndianCast::<4, u32>(x).op_bytes();
93/// let ne_bytes = *EndianCast::<4, u32>(x).ne_bytes();
94///
95/// assert_ne!(le_bytes, be_bytes);
96/// assert_ne!(ne_bytes, op_bytes);
97/// assert_eq!(le_bytes, [0x78, 0x56, 0x34, 0x12]);
98/// assert_eq!(be_bytes, [0x12, 0x34, 0x56, 0x78]);
99///
100/// // usable in const contexts
101/// const LE: [u8; 4] = EndianCast::<4, u32>(0x12345678).le_bytes();
102/// ```
103#[repr(transparent)]
104#[derive(Copy, Clone, Debug)]
105pub struct EndianCast<const N: usize, T: Sized + Copy>(pub T);
106
107impl<const N: usize, T: Sized + Copy> EndianCast<N, T> {
108    const CHECK_SIZE: bool = {
109        if core::mem::size_of::<T>() != N {
110            panic!("N must be the true byte size of T");
111        }
112        true
113    };
114
115    /// Converts the value to a little-endian byte array.
116    #[inline(always)]
117    pub const fn le_bytes(&self) -> [u8; N] {
118        #[cfg(target_endian = "little")]
119        {
120            *self.ne_bytes()
121        }
122        #[cfg(target_endian = "big")]
123        {
124            reverse_const(*self.ne_bytes())
125        }
126    }
127
128    /// Converts the value to a big-endian byte array.
129    ///
130    /// This function is a no-op on big-endian systems and reverses the bytes on little-endian
131    /// systems.
132    #[inline(always)]
133    pub const fn be_bytes(&self) -> [u8; N] {
134        #[cfg(target_endian = "little")]
135        {
136            reverse_const(*self.ne_bytes())
137        }
138        #[cfg(target_endian = "big")]
139        {
140            *self.ne_bytes()
141        }
142    }
143
144    /// Converts the value to a byte array in the opposite endianness of the system endianness.
145    ///
146    /// This function is never a no-op and always reverses the bytes.
147    #[inline(always)]
148    pub const fn op_bytes(&self) -> [u8; N] {
149        #[cfg(target_endian = "little")]
150        {
151            self.be_bytes()
152        }
153        #[cfg(target_endian = "big")]
154        {
155            self.le_bytes()
156        }
157    }
158
159    /// Returns a reference to the underlying native endian bytes of `self`.
160    #[inline(always)]
161    pub const fn ne_bytes(&self) -> &[u8; N] {
162        let _check = Self::CHECK_SIZE;
163        unsafe { &*(self as *const Self as *const [u8; N]) }
164    }
165}
166
167#[inline(always)]
168pub const fn reverse_const<const N: usize>(mut bytes: [u8; N]) -> [u8; N] {
169    let mut i = 0;
170    let mut j = N - 1;
171
172    while i < j {
173        let tmp = bytes[i];
174        bytes[i] = bytes[j];
175        bytes[j] = tmp;
176
177        i += 1;
178        j -= 1;
179    }
180
181    bytes
182}
183
184#[inline(always)]
185pub fn reverse<N: ArrayLength>(mut bytes: GenericArray<u8, N>) -> GenericArray<u8, N> {
186    let mut i = 0;
187    let mut j = N::USIZE - 1;
188
189    while i < j {
190        let tmp = bytes[i];
191        bytes[i] = bytes[j];
192        bytes[j] = tmp;
193
194        i += 1;
195        j -= 1;
196    }
197
198    bytes
199}
200
201/// The [`Endianness`] trait provides methods for converting to and from the byte
202/// representation of the type implementing the trait. It is automatically implemented for all
203/// primitive types and arrays of bytes and can easily be implemented on arbitrary types that
204/// implement [`Sized`] and [`Copy`] by manually implementing the trait. As with
205/// [`EndianCast`], you must specify the size of the type as a const generic, and providing an
206/// incorrect size will result in a panic at compile time.
207///
208/// When the system endianness matches the requested endianness, the conversion is essentially
209/// a no-op, and the bytes are returned as-is. When the system endianness does not match the
210/// requested endianness, the bytes are reversed.
211///
212/// ### Example
213/// ```
214/// use endian_cast::Endianness;
215///
216/// let x = 0x12345678u32;
217/// let le_bytes = x.le_bytes();
218/// let be_bytes = x.be_bytes();
219/// let op_bytes = x.op_bytes();
220/// let ne_bytes = *x.ne_bytes();
221/// assert_ne!(le_bytes, be_bytes);
222/// assert_ne!(ne_bytes, op_bytes);
223/// assert_eq!(le_bytes, [0x78, 0x56, 0x34, 0x12].into());
224/// assert_eq!(be_bytes, [0x12, 0x34, 0x56, 0x78].into());
225/// ```
226pub trait Endianness: Sized + Copy {
227    /// The size of this type in bytes.
228    type N: ArrayLength;
229    const CHECK_SIZE: bool = {
230        if core::mem::size_of::<Self>() != Self::N::USIZE {
231            panic!("N must be the true byte size of T");
232        }
233        true
234    };
235
236    /// Converts the value to a little-endian byte array.
237    ///
238    /// This function is a no-op on little-endian systems and reverses the bytes on big-endian
239    /// systems.
240    #[inline(always)]
241    fn le_bytes(&self) -> GenericArray<u8, Self::N> {
242        #[cfg(target_endian = "little")]
243        {
244            self.ne_bytes().clone()
245        }
246        #[cfg(target_endian = "big")]
247        {
248            reverse(self.ne_bytes().clone())
249        }
250    }
251
252    /// Converts the value to a big-endian byte array.
253    ///
254    /// This function is a no-op on big-endian systems and reverses the bytes on little-endian
255    /// systems.
256    #[inline(always)]
257    fn be_bytes(&self) -> GenericArray<u8, Self::N> {
258        #[cfg(target_endian = "little")]
259        {
260            reverse(self.ne_bytes().clone())
261        }
262        #[cfg(target_endian = "big")]
263        {
264            self.ne_bytes().clone()
265        }
266    }
267
268    /// Converts the value to a byte array in the opposite endianness of the system endianness.
269    ///
270    /// This function is never a no-op and always reverses the bytes.
271    #[inline(always)]
272    fn op_bytes(&self) -> GenericArray<u8, Self::N> {
273        #[cfg(target_endian = "little")]
274        {
275            self.be_bytes()
276        }
277        #[cfg(target_endian = "big")]
278        {
279            self.le_bytes()
280        }
281    }
282
283    /// Returns a reference to the underlying native endian bytes of `self`.
284    #[inline(always)]
285    fn ne_bytes(&self) -> &GenericArray<u8, Self::N> {
286        let _check = Self::CHECK_SIZE;
287        unsafe { &*(self as *const Self as *const GenericArray<u8, Self::N>) }
288    }
289}
290
291impl Endianness for u8 {
292    type N = U1;
293}
294impl Endianness for i8 {
295    type N = U1;
296}
297impl Endianness for u16 {
298    type N = U2;
299}
300impl Endianness for i16 {
301    type N = U2;
302}
303impl Endianness for u32 {
304    type N = U4;
305}
306impl Endianness for i32 {
307    type N = U4;
308}
309impl Endianness for u64 {
310    type N = U8;
311}
312impl Endianness for i64 {
313    type N = U8;
314}
315impl Endianness for u128 {
316    type N = U16;
317}
318impl Endianness for i128 {
319    type N = U16;
320}
321impl Endianness for f32 {
322    type N = U4;
323}
324impl Endianness for f64 {
325    type N = U8;
326}
327impl Endianness for bool {
328    type N = U1;
329}
330impl Endianness for char {
331    type N = U1;
332}
333#[cfg(target_pointer_width = "32")]
334impl Endianness for usize {
335    type N = U4;
336}
337#[cfg(target_pointer_width = "64")]
338impl Endianness for usize {
339    type N = U8;
340}
341#[cfg(target_pointer_width = "32")]
342impl Endianness for isize {
343    type N = U4;
344}
345#[cfg(target_pointer_width = "64")]
346impl Endianness for isize {
347    type N = U8;
348}
349
350impl<N: ArrayLength> Endianness for GenericArray<u8, N>
351where
352    <N as ArrayLength>::ArrayType<u8>: core::marker::Copy,
353{
354    type N = N;
355}
356
357#[test]
358fn test_endianness() {
359    let x = 0x12345678u32;
360    let le_bytes = x.le_bytes();
361    let be_bytes = x.be_bytes();
362    assert_eq!(le_bytes, [0x78, 0x56, 0x34, 0x12].into());
363    assert_eq!(be_bytes, [0x12, 0x34, 0x56, 0x78].into());
364    assert_eq!(x.le_bytes(), le_bytes);
365    assert_eq!(x.be_bytes(), be_bytes);
366    #[cfg(target_endian = "little")]
367    {
368        assert_eq!(x.ne_bytes(), &le_bytes);
369        assert_eq!(x.op_bytes(), be_bytes);
370    }
371    #[cfg(target_endian = "big")]
372    {
373        assert_eq!(x.ne_bytes(), &be_bytes);
374        assert_eq!(x.op_bytes(), le_bytes);
375    }
376}
377
378#[test]
379fn test_endian_cast() {
380    let x = 0x12345678u32;
381    let cast = EndianCast::<4, u32>(x);
382    let le_bytes = cast.le_bytes();
383    let be_bytes = cast.be_bytes();
384    assert_eq!(le_bytes, [0x78, 0x56, 0x34, 0x12]);
385    assert_eq!(be_bytes, [0x12, 0x34, 0x56, 0x78]);
386    #[cfg(target_endian = "little")]
387    {
388        assert_eq!(*cast.ne_bytes(), le_bytes);
389        assert_eq!(cast.op_bytes(), be_bytes);
390    }
391    #[cfg(target_endian = "big")]
392    {
393        assert_eq!(*cast.ne_bytes(), be_bytes);
394        assert_eq!(cast.op_bytes(), le_bytes);
395    }
396}