|
1 | | -pub struct HexDisplay<T>(T); |
| 1 | +pub struct HexDisplay([u8; 32]); |
2 | 2 |
|
3 | | -impl<T: AsRef<[u8]>> core::fmt::Display for HexDisplay<T> { |
| 3 | +impl core::fmt::Debug for HexDisplay { |
4 | 4 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
5 | | - for b in self.0.as_ref() { |
6 | | - write!(f, "{b:02x}")?; |
7 | | - } |
8 | | - Ok(()) |
| 5 | + std::fmt::Display::fmt(self, f) |
| 6 | + } |
| 7 | +} |
| 8 | + |
| 9 | +impl core::fmt::Display for HexDisplay { |
| 10 | + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 11 | + // this implementation avoids any heap allocations and is optimized by |
| 12 | + // the compiler to use vectorized instructions where available. |
| 13 | + // |
| 14 | + // with O3, the loop is unrolled and vectorized to use SIMD instructions |
| 15 | + // |
| 16 | + // https://rust.godbolt.org/z/seM19zEfv |
| 17 | + |
| 18 | + let mut buf = [0u8; 64]; |
| 19 | + // SAFETY: 64 is evenly divisible by 2 |
| 20 | + unsafe { buf.as_chunks_unchecked_mut::<2>() } |
| 21 | + .iter_mut() |
| 22 | + .zip(self.0.as_ref()) |
| 23 | + .for_each(|(slot, &byte)| { |
| 24 | + *slot = byte_to_hex(byte); |
| 25 | + }); |
| 26 | + |
| 27 | + // SAFETY: buf only contains valid ASCII hex characters |
| 28 | + let buf = unsafe { core::str::from_utf8_unchecked(&buf) }; |
| 29 | + |
| 30 | + f.pad(buf) |
9 | 31 | } |
10 | 32 | } |
11 | 33 |
|
12 | | -pub trait HexDisplayExt: AsRef<[u8]> { |
13 | | - fn hex_display(self) -> HexDisplay<Self> |
14 | | - where |
15 | | - Self: Sized, |
16 | | - { |
17 | | - HexDisplay(self) |
| 34 | +const fn byte_to_hex(byte: u8) -> [u8; 2] { |
| 35 | + const unsafe fn nibble_to_hex(nibble: u8) -> u8 { |
| 36 | + match nibble { |
| 37 | + 0..=9 => b'0' + nibble, |
| 38 | + 10..=15 => b'a' + (nibble - 10), |
| 39 | + // SAFETY: invariant held by caller that nibble is in 0..=15 |
| 40 | + _ => unsafe { core::hint::unreachable_unchecked() }, |
| 41 | + } |
18 | 42 | } |
| 43 | + |
| 44 | + // SAFETY: shifting and masking ensures nibble is in 0..=15 for both calls |
| 45 | + unsafe { [nibble_to_hex(byte >> 4), nibble_to_hex(byte & 0x0F)] } |
19 | 46 | } |
20 | 47 |
|
21 | | -impl<T: AsRef<[u8]>> HexDisplayExt for T {} |
| 48 | +pub trait HexDisplayExt { |
| 49 | + fn hex_display(self) -> HexDisplay; |
| 50 | +} |
| 51 | + |
| 52 | +impl<T: Into<[u8; 32]>> HexDisplayExt for T { |
| 53 | + fn hex_display(self) -> HexDisplay { |
| 54 | + HexDisplay(self.into()) |
| 55 | + } |
| 56 | +} |
0 commit comments