I was looking for a way to convert small amounts of hex data into an array, at compile time, I've found this: https://docs.rs/hex-literal/0.2.1/hex_literal/
But we have const fn and const generics, so I've given it a try:
#![feature(const_if_match, const_loop, const_generics, const_fn, const_panic)]
#![allow(incomplete_features)]
const fn from_hex_len(arr: &[u8]) -> usize {
let mut i = 0;
let mut count = 0;
while i < arr.len() {
match arr[i] {
b'0' ..= b'9' | b'a' ..= b'f' | b'A' ..= b'F' => { count += 1; },
b' ' | b'\r' | b'\n' | b'\t' => {}, // Ignored.
_ => { panic!("from_hex_len: found a disallowed byte."); },
}
i += 1;
}
if count % 2 != 0 {
panic!("from_hex_len: count % 2 != 0.");
}
count / 2
}
/// It accepts in the input data only:
/// '0'..='9', 'a'..='f', 'A'..='F' hex characters which will be used.
/// ' ', '\r', '\n', '\t' formatting characters which will be ignored.
const fn from_hex<const N: usize>(arr: &[u8]) -> [u8; N] {
let mut result = [b'\0'; N];
let mut i = 0;
let mut count = 0;
let mut pred: u8 = 0;
while i < arr.len() {
match arr[i] {
b'0' ..= b'9' => {
if count % 2 == 1 {
result[count / 2] = pred * 16 + arr[i] - b'0';
} else {
pred = arr[i] - b'0';
}
count += 1;
},
b'a' ..= b'f' => {
if count % 2 == 1 {
result[count / 2] = pred * 16 + arr[i] - b'a' + 10;
} else {
pred = arr[i] - b'a' + 10;
}
count += 1;
},
b'A' ..= b'F' => {
if count % 2 == 1 {
result[count / 2] = pred * 16 + arr[i] - b'A' + 10;
} else {
pred = arr[i] - b'A' + 10;
}
count += 1;
},
b' ' | b'\r' | b'\n' | b'\t' => {}, // Ignored.
_ => { panic!("from_hex: found a disallowed byte."); },
}
i += 1;
}
if count != N * 2 {
panic!("from_hex: count != N * 2.");
}
result
}
fn main() {
const S: &[u8] =
b"08021661260f0028004b0405074e340c324d5b083131632811
5112393c571128622b453004383e0051311f49374f0e1d5d47
284335581e03310d244134465f17043c0b2a45184438012038
472502245b161f104733433f59295c24361628281c42210d50
182f203c63032d022c4b21354e24541423110c322062511c40
17430a1a2628433b36464212264046431a1444023e0c145f3f
5e273f08285b42315e1518373a054249631a61114e4e60530e
5822593f48152417094b004c2c142d230e003d2161221f215f
4e11351c164b1f430f5e0350043e100e0935385c1027052a60
231f2f373a581800113618241d55395638003023475907052c
2c252c3c153a3336113a13505144055e2f451c495c0d563411
4d04593728043408536123631007613920101a1a4f211b6242
58244457393e1448032e21432e370c203f5d3545042a104926
19270b185e4812082e1d20283e4c2414452429481e1758223e
634552433b554a0424101449231d4e1f5a014a1f3147305651
10173905360146364753333645105c21303d2b340159134330";
const SH: [u8; from_hex_len(S)] = from_hex(S);
println!("{:?}", &SH[..]);
}
It seems to work well enough (I haven't tested it for larger amounts of data or tons of conversions in a single program) but having two different functions with that API isn't great. Do you know if we can improve that with the features Rust offers now?
Looking forward to possible future features I think the problem could be solved with some kind of 'unsized constants', where the length isn't written in the source code but it's still a compile-time constant:
const fn from_hex<const N: usize>(arr: &[u8]) -> [u8] {...}
const SH: [u8] = from_hex(b"08021661260f00");