Skip to main content

lexe_std/
const_utils.rs

1//! Utilities for use in `const` fns and expressions.
2
3use std::mem::MaybeUninit;
4
5/// Assert at compile that that a boolean expression evaluates to true.
6/// Implementation copied from the static_assertions crate.
7#[macro_export]
8macro_rules! const_assert {
9    ($x:expr $(,)?) => {
10        #[allow(clippy::const_is_empty, clippy::eq_op, unknown_lints)]
11        const _: [(); 0 - !{
12            const CONST_ASSERT: bool = $x;
13            CONST_ASSERT
14        } as usize] = [];
15    };
16}
17
18/// Assert at compile time that two `usize` values are equal. This assert has a
19/// nice benefit where there compiler error will actually _print out_ the
20/// two values.
21#[macro_export]
22macro_rules! const_assert_usize_eq {
23    ($x:expr, $y:expr $(,)?) => {
24        const _: [(); $x] = [(); $y];
25    };
26}
27
28/// Assert at compile time that a type has a specific in-memory size in bytes.
29///
30/// The assertion is only effective on 64-bit archs since we don't care about
31/// 32-bit Android ARM (the only 32-bit arch we compile to, and making the
32/// assertion more complicated just for that is not worth it).
33///
34/// Usage: `const_assert_mem_size!(u64, 8);`
35#[macro_export]
36macro_rules! const_assert_mem_size {
37    ($type:ty, $size:expr $(,)?) => {
38        #[cfg(target_pointer_width = "64")]
39        $crate::const_assert_usize_eq!(::core::mem::size_of::<$type>(), $size);
40    };
41}
42
43/// [`Result::unwrap`] but works in `const fn`.
44// TODO(phlip9): remove this when const unwrap stabilizes
45pub const fn const_result_unwrap<T: Copy, E: Copy>(result: Result<T, E>) -> T {
46    match result {
47        Ok(result) => result,
48        Err(_) => panic!("unwrap on Err"),
49    }
50}
51
52/// Compile-time cast from `&T::From` to `&T`, where `T` is just a struct with
53/// a single field of type `T::From` and `T` is `#[repr(transparent)]`.
54///
55/// Useful for casting a new-type's inner struct reference to a new-type
56/// reference.
57///
58/// See [`ref_cast`] for more details. Just use `T::ref_cast` if you don't need
59/// `const`.
60///
61/// ## Example
62///
63/// ```rust
64/// use lexe_std::const_utils;
65/// use ref_cast::RefCast;
66///
67/// #[derive(RefCast)]
68/// #[repr(transparent)]
69/// struct Id(u32);
70///
71/// // Safe, const cast from `&123` to `&Id(123)`
72/// const MY_ID: &'static Id = const_utils::const_ref_cast(&123);
73/// ```
74#[cfg(feature = "ref-cast")]
75pub const fn const_ref_cast<T: ref_cast::RefCast>(from: &T::From) -> &T {
76    // SAFETY: we require that `T: RefCast`, which guarantees that this cast is
77    // safe. Unfortunately we need this extra method as `T::ref_cast` is not
78    // currently const (Rust doesn't support const traits yet).
79    unsafe { &*(from as *const T::From as *const T) }
80}
81
82/// Easily concatenate multiple const `&str` slices into a single const `&str`.
83///
84/// Unlike the `concat!` macro, which only works with string literals, this
85/// works with any const `&str` expressions.
86///
87/// ### Example
88///
89/// ```rust
90/// use lexe_std::const_concat_str;
91///
92/// const NAME: &str = const_concat_str!("HUGE", " ", "MAN");
93/// const GREETING: &str = const_concat_str!("Hello ", NAME, "!!");
94/// assert_eq!(GREETING, "Hello HUGE MAN!!");
95/// ```
96#[macro_export]
97macro_rules! const_concat_str {
98    ($($s:expr),* $(,)?) => {{
99        use $crate::std::primitive::{str, u8};
100        // ensure all inputs are &str
101        $(const _: &str = $s;)*
102        const LEN: usize = 0 $(+ $s.len())*;
103        const ARR: [u8; LEN] = $crate::const_utils::const_concat_inner::<LEN, _>(
104            &[$($s.as_bytes()),*]
105        );
106        // SAFETY: all bytes are valid UTF-8 as they come from &str slices.
107        unsafe { $crate::std::str::from_utf8_unchecked(&ARR) }
108    }};
109}
110
111/// Easily concatenate multiple const `&[u8]` slices into a single const
112/// `&[u8]`.
113//
114// TODO(phlip9): replace with `concat_bytes!` when it stabilizes.
115#[macro_export]
116macro_rules! const_concat_bytes {
117    ($($slice:expr),* $(,)?) => {{
118        use $crate::std::primitive::u8;
119        // ensure all inputs are &[u8]
120        $(const _: &[u8] = $slice;)*
121        const LEN: usize = 0 $(+ $slice.len())*;
122        const ARR: [u8; LEN] = $crate::const_utils::const_concat_inner::<LEN, _>(
123            &[$($slice),*]
124        );
125        &ARR
126    }};
127}
128
129/// Internal helper for `const_concat_str!` and `const_concat_bytes!`.
130#[doc(hidden)]
131pub const fn const_concat_inner<const LEN: usize, T: Copy>(
132    slices: &[&[T]],
133) -> [T; LEN] {
134    let mut arr: [MaybeUninit<T>; LEN] = [MaybeUninit::uninit(); LEN];
135    let mut base = 0;
136    let mut i = 0;
137    while i < slices.len() {
138        let slice = slices[i];
139        let mut j = 0;
140        while j < slice.len() {
141            arr[base + j] = MaybeUninit::new(slice[j]);
142            j += 1;
143        }
144        base += slice.len();
145        i += 1;
146    }
147    if base != LEN {
148        panic!("invalid length");
149    }
150    // SAFETY: all elements have been initialized.
151    // TODO(phlip9): replace with `MaybeUninit::array_assume_init` when stable.
152    unsafe { std::mem::transmute_copy::<[MaybeUninit<T>; LEN], [T; LEN]>(&arr) }
153}