bytify/
lib.rs

1//! This crate provides a simple macro that can take multiple const-expr values as an input and
2//! merge them all together into a continuous byte array at compile-time.
3//! Supports all primitive types and is #\[no_std\] compatible.
4//!
5//! ```rust
6//! use bytify::bytify;
7//!
8//! const CAKE: char = '🎂';
9//!
10//! assert_eq!(bytify!("The ", CAKE, " is a lie!"), [
11//!     b'T', b'h', b'e',
12//!     b' ',
13//!     0xF0, 0x9F, 0x8E, 0x82,
14//!     b' ',
15//!     b'i', b's',
16//!     b' ',
17//!     b'a',
18//!     b' ',
19//!     b'l', b'i', b'e',
20//!     b'!',
21//! ]);
22//! ```
23//!
24//! ### Supported types
25//!
26//! All primitive types (as literals or any other const-expr values) as well as `[u8; N]` and `&[u8]` values.
27//!
28//! * Unsuffixed numbers are inferred to be `i32` for integers and `f64` for floats.
29//! * `bool` values are cast to `u8`.
30//! * UTF-8 encoding is used to write `str` and `char` values.
31//!
32//! ### Endianness
33//!
34//! [`bytify!`] always writes data using current target's native endianness.
35//! To switch to little/big endianness use [`bytify_le!`] or [`bytify_be!`] respectively.
36//!
37//! ```rust
38//! use bytify::{bytify_be, bytify_le};
39//!
40//! assert_eq!(bytify_le!(1), [0x01, 0x00, 0x00, 0x00]);
41//! assert_eq!(bytify_be!(1), [0x00, 0x00, 0x00, 0x01]);
42//! ```
43//!
44//! ### Constants and statics
45//!
46//! ```rust
47//! use bytify::bytify;
48//!
49//! bytify!(const NO_EVIL: '🙈', '🙉', '🙊');
50//! assert_eq!(NO_EVIL, [0xF0, 0x9F, 0x99, 0x88, 0xF0, 0x9F, 0x99, 0x89, 0xF0, 0x9F, 0x99, 0x8A]);
51//! ```
52
53#![no_std]
54#![warn(
55    absolute_paths_not_starting_with_crate,
56    meta_variable_misuse,
57    missing_debug_implementations,
58    missing_docs,
59    noop_method_call,
60    pointer_structural_match,
61    unreachable_pub,
62    unused_crate_dependencies,
63    unused_lifetimes,
64    clippy::cast_lossless,
65    clippy::cast_possible_truncation,
66    clippy::cast_possible_wrap,
67    clippy::cast_precision_loss,
68    clippy::cast_sign_loss,
69    clippy::checked_conversions,
70    clippy::cognitive_complexity,
71    clippy::exhaustive_enums,
72    clippy::exhaustive_structs,
73    clippy::future_not_send,
74    clippy::inconsistent_struct_constructor,
75    clippy::inefficient_to_string,
76    clippy::use_debug,
77    clippy::use_self
78)]
79
80use typewit::{HasTypeWitness, MakeTypeWitness, TypeEq, TypeWitnessTypeArg};
81
82#[derive(Debug)]
83#[non_exhaustive]
84#[doc(hidden)]
85pub enum Endianness {
86    Native,
87    Big,
88    Little,
89}
90
91/// This macro is a big endianness version of [`bytify!`] macro.
92#[macro_export]
93macro_rules! bytify_be {
94    ($visibility:vis const $name:ident: $($items:expr),* $(,)?) => {
95        $visibility const $name: [::core::primitive::u8; $crate::bytify!(priv len: $($items),*)] = $crate::bytify!(priv endianness: Big => $($items),*);
96    };
97    ($visibility:vis static $name:ident: $($items:expr),* $(,)?) => {
98        $visibility static $name: [::core::primitive::u8; $crate::bytify!(priv len: $($items),*)] = $crate::bytify!(priv endianness: Big => $($items),*);
99    };
100    ($($items:expr),* $(,)?) => {
101        $crate::bytify!(priv endianness: Big => $($items),*)
102    };
103}
104
105/// This macro is a little endianness version of [`bytify!`] macro.
106#[macro_export]
107macro_rules! bytify_le {
108    ($visibility:vis const $name:ident: $($items:expr),* $(,)?) => {
109        $visibility const $name: [::core::primitive::u8; $crate::bytify!(priv len: $($items),*)] = $crate::bytify!(priv endianness: Little => $($items),*);
110    };
111    ($visibility:vis static $name:ident: $($items:expr),* $(,)?) => {
112        $visibility static $name: [::core::primitive::u8; $crate::bytify!(priv len: $($items),*)] = $crate::bytify!(priv endianness: Little => $($items),*);
113    };
114    ($($items:expr),* $(,)?) => {
115        $crate::bytify!(priv endianness: Little => $($items),*)
116    };
117}
118
119/// This macro can take multiple const-expr values as an input and merge them all together into
120/// a continuous byte array at compile-time. Supports all primitive types and is #\[no_std\] compatible.
121///
122/// ```rust
123/// use bytify::bytify;
124///
125/// const CAKE: char = '🎂';
126///
127/// assert_eq!(bytify!("The ", CAKE, " is a lie!"), [
128///     b'T', b'h', b'e',
129///     b' ',
130///     0xF0, 0x9F, 0x8E, 0x82,
131///     b' ',
132///     b'i', b's',
133///     b' ',
134///     b'a',
135///     b' ',
136///     b'l', b'i', b'e',
137///     b'!',
138/// ]);
139/// ```
140///
141/// ### Supported types
142///
143/// All primitive types (as literals or any other const-expr values) as well as `[u8; N]` and `&[u8]` values.
144///
145/// * Unsuffixed numbers are inferred to be `i32` for integers and `f64` for floats.
146/// * `bool` values are cast to `u8`.
147/// * UTF-8 encoding is used to write `str` and `char` values.
148///
149/// ### Endianness
150///
151/// [`bytify!`] always writes data using current target's native endianness.
152/// To switch to little/big endianness use [`bytify_le!`] or [`bytify_be!`] respectively.
153///
154/// ```rust
155/// use bytify::{bytify_be, bytify_le};
156///
157/// assert_eq!(bytify_le!(1), [0x01, 0x00, 0x00, 0x00]);
158/// assert_eq!(bytify_be!(1), [0x00, 0x00, 0x00, 0x01]);
159/// ```
160///
161/// ### Constants and statics
162///
163/// ```rust
164/// use bytify::bytify;
165///
166/// bytify!(const NO_EVIL: '🙈', '🙉', '🙊');
167/// assert_eq!(NO_EVIL, [0xF0, 0x9F, 0x99, 0x88, 0xF0, 0x9F, 0x99, 0x89, 0xF0, 0x9F, 0x99, 0x8A]);
168/// ```
169#[macro_export]
170macro_rules! bytify {
171    ($visibility:vis const $name:ident: $($items:expr),* $(,)?) => {
172        $visibility const $name: [::core::primitive::u8; $crate::bytify!(priv len: $($items),*)] = $crate::bytify!(priv endianness: Native => $($items),*);
173    };
174    ($visibility:vis static $name:ident: $($items:expr),* $(,)?) => {
175        $visibility static $name: [::core::primitive::u8; $crate::bytify!(priv len: $($items),*)] = $crate::bytify!(priv endianness: Native => $($items),*);
176    };
177    ($($items:expr),* $(,)?) => {
178        $crate::bytify!(priv endianness: Native => $($items),*)
179    };
180    (priv endianness: $endianness:ident => $($items:expr),*) => {
181        $crate::bytify!(priv concat: $(($crate::bytify_len($items), $crate::bytify_one($crate::Endianness::$endianness, $items))),*)
182    };
183    (priv len: $($items:expr),*) => {
184        0 $(+ $crate::bytify_len($items))*
185    };
186    (priv concat: $(($len:expr, $bytes:expr)),*) => {
187        {
188            const LEN: ::core::primitive::usize = 0 $(+ $len)*;
189            const SUM: [::core::primitive::u8; LEN] = {
190                let mut sum: [::core::primitive::u8; LEN] = [0; LEN];
191                let mut offset: ::core::primitive::usize = 0;
192                $(
193                    {
194                        const LEN: ::core::primitive::usize = $len;
195                        const BYTES: [::core::primitive::u8; $len] = $bytes;
196                        let mut i = 0;
197                        while i < LEN {
198                            sum[offset + i] = BYTES[i];
199                            i += 1;
200                        }
201                        offset += LEN;
202                    }
203                )*
204                sum
205            };
206            SUM
207        }
208    };
209    (priv $($unhandled:tt)*) => {
210        ::core::compile_error!(::core::stringify!(unhandled input: $($unhandled)*))
211    };
212}
213
214#[doc(hidden)]
215pub const fn bytify_len<const X: usize, T>(value: T) -> usize
216where
217    T: HasTypeWitness<BytifyWitness<X, T>>,
218{
219    match T::WITNESS {
220        BytifyWitness::bool(witness) => bool::len(witness.to_right(value)),
221        BytifyWitness::char(witness) => char::len(witness.to_right(value)),
222        BytifyWitness::u8(witness) => u8::len(witness.to_right(value)),
223        BytifyWitness::i8(witness) => i8::len(witness.to_right(value)),
224        BytifyWitness::u16(witness) => u16::len(witness.to_right(value)),
225        BytifyWitness::i16(witness) => i16::len(witness.to_right(value)),
226        BytifyWitness::u32(witness) => u32::len(witness.to_right(value)),
227        BytifyWitness::i32(witness) => i32::len(witness.to_right(value)),
228        BytifyWitness::u64(witness) => u64::len(witness.to_right(value)),
229        BytifyWitness::i64(witness) => i64::len(witness.to_right(value)),
230        BytifyWitness::u128(witness) => u128::len(witness.to_right(value)),
231        BytifyWitness::i128(witness) => i128::len(witness.to_right(value)),
232        BytifyWitness::usize(witness) => usize::len(witness.to_right(value)),
233        BytifyWitness::isize(witness) => isize::len(witness.to_right(value)),
234        BytifyWitness::f32(witness) => f32::len(witness.to_right(value)),
235        BytifyWitness::f64(witness) => f64::len(witness.to_right(value)),
236        BytifyWitness::str(witness) => str::len(witness.to_right(value)),
237        BytifyWitness::SliceArray(witness) => slice_array::len(witness.to_right(value)),
238        BytifyWitness::Array(witness) => array::len(witness.to_right(value)),
239    }
240}
241
242#[doc(hidden)]
243pub const fn bytify_one<const X: usize, const N: usize, T>(
244    endianness: Endianness,
245    value: T,
246) -> [u8; N]
247where
248    T: HasTypeWitness<BytifyWitness<X, T>>,
249{
250    match T::WITNESS {
251        BytifyWitness::bool(witness) => bool::copy(endianness, witness.to_right(value)),
252        BytifyWitness::char(witness) => char::copy(endianness, witness.to_right(value)),
253        BytifyWitness::u8(witness) => u8::copy(endianness, witness.to_right(value)),
254        BytifyWitness::i8(witness) => i8::copy(endianness, witness.to_right(value)),
255        BytifyWitness::u16(witness) => u16::copy(endianness, witness.to_right(value)),
256        BytifyWitness::i16(witness) => i16::copy(endianness, witness.to_right(value)),
257        BytifyWitness::u32(witness) => u32::copy(endianness, witness.to_right(value)),
258        BytifyWitness::i32(witness) => i32::copy(endianness, witness.to_right(value)),
259        BytifyWitness::u64(witness) => u64::copy(endianness, witness.to_right(value)),
260        BytifyWitness::i64(witness) => i64::copy(endianness, witness.to_right(value)),
261        BytifyWitness::u128(witness) => u128::copy(endianness, witness.to_right(value)),
262        BytifyWitness::i128(witness) => i128::copy(endianness, witness.to_right(value)),
263        BytifyWitness::usize(witness) => usize::copy(endianness, witness.to_right(value)),
264        BytifyWitness::isize(witness) => isize::copy(endianness, witness.to_right(value)),
265        BytifyWitness::f32(witness) => f32::copy(endianness, witness.to_right(value)),
266        BytifyWitness::f64(witness) => f64::copy(endianness, witness.to_right(value)),
267        BytifyWitness::str(witness) => str::copy(endianness, witness.to_right(value)),
268        BytifyWitness::SliceArray(witness) => {
269            slice_array::copy(endianness, witness.to_right(value))
270        },
271        BytifyWitness::Array(witness) => array::copy(endianness, witness.to_right(value)),
272    }
273}
274
275#[derive(Debug)]
276#[non_exhaustive]
277#[doc(hidden)]
278#[allow(non_camel_case_types)]
279pub enum BytifyWitness<const X: usize, Witness: ?Sized> {
280    bool(TypeEq<Witness, bool>),
281    char(TypeEq<Witness, char>),
282    u8(TypeEq<Witness, u8>),
283    i8(TypeEq<Witness, i8>),
284    u16(TypeEq<Witness, u16>),
285    i16(TypeEq<Witness, i16>),
286    u32(TypeEq<Witness, u32>),
287    i32(TypeEq<Witness, i32>),
288    u64(TypeEq<Witness, u64>),
289    i64(TypeEq<Witness, i64>),
290    u128(TypeEq<Witness, u128>),
291    i128(TypeEq<Witness, i128>),
292    usize(TypeEq<Witness, usize>),
293    isize(TypeEq<Witness, isize>),
294    f32(TypeEq<Witness, f32>),
295    f64(TypeEq<Witness, f64>),
296    str(TypeEq<Witness, &'static str>),
297    SliceArray(TypeEq<Witness, &'static [u8]>),
298    Array(TypeEq<Witness, [u8; X]>),
299}
300
301impl<const X: usize, Witness> TypeWitnessTypeArg for BytifyWitness<X, Witness> {
302    type Arg = Witness;
303}
304
305mod bool {
306    use super::*;
307
308    pub(crate) const fn len(value: bool) -> usize {
309        u8::len(value as u8)
310    }
311
312    pub(crate) const fn copy<const N: usize>(endianness: Endianness, value: bool) -> [u8; N] {
313        u8::copy(endianness, value as u8)
314    }
315
316    impl MakeTypeWitness for BytifyWitness<0, bool> {
317        const MAKE: Self = Self::bool(TypeEq::NEW);
318    }
319}
320
321mod char {
322    use super::*;
323
324    pub(crate) const fn len(value: char) -> usize {
325        value.len_utf8()
326    }
327
328    pub(crate) const fn copy<const N: usize>(endianness: Endianness, value: char) -> [u8; N] {
329        array::copy(endianness, bytes::<N>(value))
330    }
331
332    const fn bytes<const N: usize>(char: char) -> [u8; N] {
333        const TAG_CONT: u8 = 0b1000_0000;
334        const TAG_TWO_B: u8 = 0b1100_0000;
335        const TAG_THREE_B: u8 = 0b1110_0000;
336        const TAG_FOUR_B: u8 = 0b1111_0000;
337        let mut array = [0; N];
338        let code = char as u32;
339        match code {
340            ..= 0x0000007F => {
341                array[0] = (code & 0xFF) as u8;
342            },
343            0x00000080 ..= 0x000007FF => {
344                array[0] = (code >> 6 & 0x1F) as u8 | TAG_TWO_B;
345                array[1] = (code & 0x3F) as u8 | TAG_CONT;
346            },
347            0x00000800 ..= 0x0000FFFF => {
348                array[0] = (code >> 12 & 0x0F) as u8 | TAG_THREE_B;
349                array[1] = (code >> 6 & 0x3F) as u8 | TAG_CONT;
350                array[2] = (code & 0x3F) as u8 | TAG_CONT;
351            },
352            0x00010000 .. => {
353                array[0] = (code >> 18 & 0x07) as u8 | TAG_FOUR_B;
354                array[1] = (code >> 12 & 0x3F) as u8 | TAG_CONT;
355                array[2] = (code >> 6 & 0x3F) as u8 | TAG_CONT;
356                array[3] = (code & 0x3F) as u8 | TAG_CONT;
357            },
358        }
359        array
360    }
361
362    impl MakeTypeWitness for BytifyWitness<0, char> {
363        const MAKE: Self = Self::char(TypeEq::NEW);
364    }
365}
366
367macro_rules! bytify_integer {
368    ($name:ident) => {
369        mod $name {
370            use super::*;
371
372            pub(crate) const fn len(value: $name) -> usize {
373                value.to_ne_bytes().len()
374            }
375
376            pub(crate) const fn copy<const N: usize>(
377                endianness: Endianness,
378                value: $name,
379            ) -> [u8; N] {
380                let bytes = match endianness {
381                    Endianness::Native => value.to_ne_bytes(),
382                    Endianness::Big => value.to_be_bytes(),
383                    Endianness::Little => value.to_le_bytes(),
384                };
385                let mut array = [0; N];
386                let mut i = 0;
387                while i < bytes.len() {
388                    array[i] = bytes[i];
389                    i += 1;
390                }
391                array
392            }
393
394            impl MakeTypeWitness for BytifyWitness<0, $name> {
395                const MAKE: Self = Self::$name(TypeEq::NEW);
396            }
397        }
398    };
399}
400
401bytify_integer!(u8);
402bytify_integer!(i8);
403bytify_integer!(u16);
404bytify_integer!(i16);
405bytify_integer!(u32);
406bytify_integer!(i32);
407bytify_integer!(u64);
408bytify_integer!(i64);
409bytify_integer!(u128);
410bytify_integer!(i128);
411bytify_integer!(usize);
412bytify_integer!(isize);
413
414macro_rules! bytify_float {
415    ($name:ident, $bits:ident) => {
416        mod $name {
417            use super::*;
418            use core::mem::transmute;
419
420            pub(crate) const fn len(value: $name) -> usize {
421                $bits::len(bits(value))
422            }
423
424            pub(crate) const fn copy<const N: usize>(
425                endianness: Endianness,
426                value: $name,
427            ) -> [u8; N] {
428                $bits::copy(endianness, bits(value))
429            }
430
431            const fn bits(value: $name) -> $bits {
432                unsafe { transmute::<$name, $bits>(value) }
433            }
434
435            impl MakeTypeWitness for BytifyWitness<0, $name> {
436                const MAKE: Self = Self::$name(TypeEq::NEW);
437            }
438        }
439    };
440}
441
442bytify_float!(f32, u32);
443bytify_float!(f64, u64);
444
445mod str {
446    use super::*;
447
448    pub(crate) const fn len(value: &str) -> usize {
449        slice_array::len(value.as_bytes())
450    }
451
452    pub(crate) const fn copy<const N: usize>(endianness: Endianness, value: &str) -> [u8; N] {
453        slice_array::copy(endianness, value.as_bytes())
454    }
455
456    impl MakeTypeWitness for BytifyWitness<0, &'static str> {
457        const MAKE: Self = Self::str(TypeEq::NEW);
458    }
459}
460
461mod slice_array {
462    use super::*;
463
464    pub(crate) const fn len(value: &[u8]) -> usize {
465        value.len()
466    }
467
468    pub(crate) const fn copy<const N: usize>(_: Endianness, value: &[u8]) -> [u8; N] {
469        let mut array = [0; N];
470        let mut i = 0;
471        while i < value.len() {
472            array[i] = value[i];
473            i += 1;
474        }
475        array
476    }
477
478    impl MakeTypeWitness for BytifyWitness<0, &'static [u8]> {
479        const MAKE: Self = Self::SliceArray(TypeEq::NEW);
480    }
481}
482
483mod array {
484    use super::*;
485
486    pub(crate) const fn len<const X: usize>(value: [u8; X]) -> usize {
487        slice_array::len(&value)
488    }
489
490    pub(crate) const fn copy<const X: usize, const N: usize>(
491        endianness: Endianness,
492        value: [u8; X],
493    ) -> [u8; N] {
494        slice_array::copy(endianness, &value)
495    }
496
497    impl<const X: usize> MakeTypeWitness for BytifyWitness<X, [u8; X]> {
498        const MAKE: Self = Self::Array(TypeEq::NEW);
499    }
500}