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}