1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
//! Hex literals without proc macros.
//!
//! This crate implements minimalistic hex literal macros without use of proc macros.
//! The advantages are much faster compile times, ability to work with non-literal const values and
//! easier auditing.
//! However, because of the use of `const fn` the crate has some limitations depending on the Rust
//! version.
//!
//! Either way, the resulting type is a byte array (`[u8; N]`) that doesn't force you to write down
//! its length. This is already very useful since the compiler can prove the length and you avoid
//! runtime allocations.
//!
//! The crate is `no_std` and does **not** require an allocator.
//!
//! ## Usage
//!
//! Just pass a `&str` *constant* (usually a literal) into the [`hex`] macro.
//!
//! Example
//!
//! ```rust
//! use hex_lit::hex;
//!
//! let array = hex!("2a15ff");
//! assert_eq!(&array, &[42, 21, 255]);
//!
//! ```
//!
//! The input MUST NOT contain any spaces or other separators and it MUST have even length.
//! Note that you can still separate long strings into chunks using the [`concat`] macro:
//!
//! ```rust
//! use hex_lit::hex;
//!
//! let array = hex!(concat!(
//! "0000002a000000",
//! "ffffffffffffff",
//! ));
//! assert_eq!(&array, &[0, 0, 0, 42, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255]);
//!
//! ```
//!
//! ## Features depending on Rust version
//!
//! * 1.41.1+ - the MSRV, use in const contexts is impossible, only the [`hex!`] macro is available.
//! * 1.46.0+ - usage in const contexts is available and (regardless of cargo features) correctness
//! of input is checked at compile time.
//! * 1.57+ - nicer error messages for bad inputs (regardless of cargo features)
//!
//! ## Cargo features
//!
//! * `rust_v_1_46` - acknowledges bumping MSRV to 1.46+ and enables usage in const context.
//!
//! Bumping MSRV is intentionally explicit.
//!
//! Because of improved input checking it is recommended to use Rust 1.46+, prefereably 1.57+ in CI
//! even if your targeted MSRV is lower.
//!
//! [`concat`]: core::concat
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
// makes the passed function optionally const depending on rust version
macro_rules! maybe_const {
($($fun:tt)*) => {
#[cfg(rust_v_1_46)]
#[track_caller]
#[inline]
const $($fun)*
#[cfg(not(rust_v_1_46))]
$($fun)*
};
}
// makes the passed function public and optionally const depending on rust version
macro_rules! pub_maybe_const {
($($fun:tt)*) => {
// DO NOT CALL THIS OUTSIDE OF THIS CRATE!!!
#[doc(hidden)]
#[cfg(rust_v_1_46)]
#[track_caller]
#[inline]
pub const $($fun)*
// DO NOT CALL THIS OUTSIDE OF THIS CRATE!!!
#[doc(hidden)]
#[cfg(not(rust_v_1_46))]
pub $($fun)*
}
}
// Invoked when there's an invalid digit in the string to cause an error/panic
// This version is not const because const isn't supported anyway, so we just panic
#[cfg(not(rust_v_1_46))]
fn invalid_digit(digit: u8) {
panic!("invalid hex digit: ASCII {}", digit);
}
// This version makes an out-of-bounds accesses to an array to trigger compilation failure.
#[cfg(all(not(rust_v_1_57), rust_v_1_46))]
#[track_caller]
const fn invalid_digit(digit: u8) {
let digit = digit as usize;
#[allow(unknown_lints)]
#[allow(unconditional_panic)]
// We add 10000 to ensure it will panic for any digit, we want to use digit in the expression
// to display the number even though the message is messy.
let _invalid_digit = [(); 0][digit + 10000];
}
// This version panics with a nice custom message
#[cfg(rust_v_1_57)]
#[track_caller]
const fn invalid_digit(digit: u8) {
// custom message formatting because of rust limiation
let mut buf = [b'i', b'n', b'v', b'a', b'l', b'i', b'd', b' ', b'h', b'e', b'x', b' ', b'd', b'i', b'g', b'i', b't', b':', b' ', b'A', b'S', b'C', b'I', b'I', b' ', b' ', b' ', b' '];
if digit >= 100 {
buf[buf.len() - 3] = digit / 100 + b'0';
}
if digit >= 10 {
buf[buf.len() - 2] = (digit % 100) / 10 + b'0';
}
buf[buf.len() - 1] = digit % 10 + b'0';
let message = unsafe { core::str::from_utf8_unchecked(&buf) };
panic!("{}", message);
}
// Decodes a single hex digit (char) into an integer 0-15
maybe_const! {
fn decode_digit(digit: u8) -> u8 {
match digit {
b'0'..=b'9' => digit - b'0',
b'a'..=b'f' => digit - b'a' + 10,
b'A'..=b'F' => digit - b'A' + 10,
_ => {
invalid_digit(digit);
0
}
}
}
}
// Decodes a single byte in the string at given **output** position.
pub_maybe_const! {
fn decode_byte(hex: &str, pos: usize) -> u8 {
let c1 = decode_digit(hex.as_bytes()[pos * 2]);
let c2 = decode_digit(hex.as_bytes()[pos* 2 + 1]);
c1 << 4 | c2
}
}
// Internal macro implementation simplifies dispatch
// DO NOT CALL THIS OUTSIDE OF THIS CRATE!!!
#[doc(hidden)]
#[macro_export]
macro_rules! hex_impl {
($hex:expr) => {
{
const HEX: &str = $hex;
// Fails if the length is not even
const _HEX_LENGTH_MUST_BE_EVEN: () = [()][$hex.len() % 2];
let mut out = [0u8; HEX.len() / 2];
// mut refs are not allowed in const fns so we must have the decoding loop here
// we minimized its size using a separate decoding function
let mut pos = 0;
loop {
if pos >= out.len() {
break;
}
out[pos] = $crate::decode_byte(HEX, pos);
pos += 1
}
out
}
}
}
// Does nothing exept for blocking usage of hex macro in const context without the rust_v_1_46
// feature enabled.
// DO NOT CALL THIS OUTSIDE OF THIS CRATE!!!
#[doc(hidden)]
#[cfg(not(feature = "rust_v_1_46"))]
pub fn msrv_opt_in() {}
// DO NOT CALL THIS OUTSIDE OF THIS CRATE!!!
#[doc(hidden)]
#[cfg(feature = "rust_v_1_46")]
pub const fn msrv_opt_in() {}
/// Creates a byte array const value from hex &str const value.
///
/// Accepts const `&str` as an input. Refer to the crate documentation to learn more.
#[cfg(not(rust_v_1_46))]
#[macro_export]
macro_rules! hex {
($hex:expr) => {
$crate::hex_impl!($hex)
}
}
/// Creates a byte array const value from hex &str const value.
///
/// Accepts const `&str` as an input. Refer to the crate documentation to learn more.
#[cfg(rust_v_1_46)]
#[macro_export]
macro_rules! hex {
($hex:expr) => {
{
// pass through const to force const eval
const TMP: [u8; $hex.len() / 2] = $crate::hex_impl!($hex);
// block usage in const context unless the feature is explicitly turned on
$crate::msrv_opt_in();
TMP
}
}
}
/// Creates a constant byte array of given name.
///
/// This is a convenience macro so that you don't have to write out the type. However it is
/// intentionally not public - public constants must be explicitly typed to avoid accidental change
/// of the type.
///
/// ## Example
///
/// ```
/// use hex_lit::hex_const;
/// // same as writing `const FOO: [u8; 2] = [0x00, 0xff];`
/// hex_const!(FOO = "00ff");
///
/// assert_eq!(FOO, [0, 255]);
/// ```
#[cfg(feature = "rust_v_1_46")]
#[cfg_attr(docsrs, doc(cfg(feature = "rust_v_1_46")))]
#[macro_export]
macro_rules! hex_const {
($name:ident = $hex:expr) => {
const $name: [u8; $hex.len() / 2] = $crate::hex_impl!($hex);
}
}
/// Creates a static byte array of given name.
///
/// This is a convenience macro so that you don't have to write out the type. However it is
/// intentionally not public - public statics must be explicitly typed to avoid accidental change of
/// the type.
///
/// ## Example
///
/// ```
/// use hex_lit::hex_static;
/// // same as writing `static FOO: [u8; 2] = [0x00, 0xff];`
/// hex_static!(FOO = "00ff");
///
/// assert_eq!(FOO, [0, 255]);
/// ```
#[cfg(feature = "rust_v_1_46")]
#[cfg_attr(docsrs, doc(cfg(feature = "rust_v_1_46")))]
#[macro_export]
macro_rules! hex_static {
($name:ident = $hex:expr) => {
static $name: [u8; $hex.len() / 2] = $crate::hex_impl!($hex);
}
}
#[cfg(test)]
mod tests {
use super::hex;
#[test]
fn msrv_empty() {
let arr = hex!("");
assert_eq!(&arr, &[]);
}
#[test]
fn msrv_one() {
let arr = hex!("2a");
assert_eq!(&arr, &[42u8]);
}
#[test]
fn msrv_several() {
let arr = hex!("2a15ff");
assert_eq!(&arr, &[42u8, 21, 255]);
}
#[test]
fn const_val_works() {
const VAL: &str = "2a15ff";
let arr = hex!(VAL);
assert_eq!(&arr, &[42u8, 21, 255]);
}
#[test]
#[cfg(not(rust_v_1_46))]
#[should_panic]
fn invalid_digit_120() {
hex!("xx");
}
#[test]
#[cfg(not(rust_v_1_46))]
#[should_panic]
fn invalid_digit_32() {
hex!(" x");
}
}