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}