lowercase_hex/buffer.rs
1use crate::{byte2hex, imp};
2use core::fmt;
3use core::slice;
4use core::str;
5
6#[cfg(feature = "alloc")]
7#[allow(unused_imports)]
8use alloc::{string::String, vec::Vec};
9
10/// A correctly sized stack allocation for the formatted bytes to be written
11/// into.
12///
13/// `N` is the amount of bytes of the input, while `PREFIX` specifies whether
14/// the "0x" prefix is prepended to the output.
15///
16/// Note that this buffer will contain only the prefix, if specified, and null
17/// ('\0') bytes before any formatting is done.
18///
19/// # Examples
20///
21/// ```
22/// let mut buffer = lowercase_hex::Buffer::<4>::new();
23/// let printed = buffer.format(b"1234");
24/// assert_eq!(printed, "31323334");
25/// ```
26#[must_use]
27#[repr(C)]
28#[derive(Clone)]
29pub struct Buffer<const N: usize, const PREFIX: bool = false> {
30 // Workaround for Rust issue #76560:
31 // https://github.com/rust-lang/rust/issues/76560
32 // This would ideally be `[u8; (N + PREFIX as usize) * 2]`
33 prefix: [u8; 2],
34 bytes: [[u8; 2]; N],
35}
36
37impl<const N: usize, const PREFIX: bool> Default for Buffer<N, PREFIX> {
38 #[inline]
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl<const N: usize, const PREFIX: bool> fmt::Debug for Buffer<N, PREFIX> {
45 #[inline]
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.debug_tuple("Buffer").field(&self.as_str()).finish()
48 }
49}
50
51impl<const N: usize, const PREFIX: bool> Buffer<N, PREFIX> {
52 /// The length of the buffer in bytes.
53 pub const LEN: usize = (N + PREFIX as usize) * 2;
54
55 const ASSERT_SIZE: () = assert!(core::mem::size_of::<Self>() == 2 + N * 2, "invalid size");
56 const ASSERT_ALIGNMENT: () = assert!(core::mem::align_of::<Self>() == 1, "invalid alignment");
57
58 /// This is a cheap operation; you don't need to worry about reusing buffers
59 /// for efficiency.
60 #[inline]
61 pub const fn new() -> Self {
62 let () = Self::ASSERT_SIZE;
63 let () = Self::ASSERT_ALIGNMENT;
64 Self {
65 prefix: if PREFIX { [b'0', b'x'] } else { [0, 0] },
66 bytes: [[0; 2]; N],
67 }
68 }
69
70 /// Print an array of bytes into this buffer.
71 #[inline]
72 pub const fn const_format(self, array: &[u8; N]) -> Self {
73 self.const_format_inner(array)
74 }
75
76 /// Same as `encode_to_slice_inner`, but const-stable.
77 const fn const_format_inner(mut self, array: &[u8; N]) -> Self {
78 let mut i = 0;
79 while i < N {
80 let (high, low) = byte2hex(array[i]);
81 self.bytes[i][0] = high;
82 self.bytes[i][1] = low;
83 i += 1;
84 }
85 self
86 }
87
88 /// Print an array of bytes into this buffer and return a reference to its
89 /// hex string representation within the buffer.
90 #[inline]
91 pub fn format(&mut self, array: &[u8; N]) -> &mut str {
92 // length of array is guaranteed to be N.
93 self.format_inner(array)
94 }
95
96 /// Print a slice of bytes into this buffer and return a reference to its
97 /// hex string representation within the buffer.
98 ///
99 /// # Panics
100 ///
101 /// If the slice is not exactly `N` bytes long.
102 #[track_caller]
103 #[inline]
104 pub fn format_slice<T: AsRef<[u8]>>(&mut self, slice: T) -> &mut str {
105 self.format_slice_inner(slice.as_ref())
106 }
107
108 // Checks length
109 #[track_caller]
110 fn format_slice_inner(&mut self, slice: &[u8]) -> &mut str {
111 assert_eq!(slice.len(), N, "length mismatch");
112 self.format_inner(slice)
113 }
114
115 // Doesn't check length
116 #[inline]
117 fn format_inner(&mut self, input: &[u8]) -> &mut str {
118 // SAFETY: Length was checked previously;
119 // we only write only ASCII bytes.
120 unsafe {
121 let buf = self.as_mut_bytes();
122 let output = buf.as_mut_ptr().add(PREFIX as usize * 2);
123 imp::encode(input, output);
124 str::from_utf8_unchecked_mut(buf)
125 }
126 }
127
128 /// Copies `self` into a new owned `String`.
129 #[cfg(feature = "alloc")]
130 #[inline]
131 #[allow(clippy::inherent_to_string)] // this is intentional
132 pub fn to_string(&self) -> String {
133 // SAFETY: The buffer always contains valid UTF-8.
134 unsafe { String::from_utf8_unchecked(self.as_bytes().to_vec()) }
135 }
136
137 /// Returns a reference to the underlying bytes casted to a string slice.
138 #[inline]
139 pub const fn as_str(&self) -> &str {
140 // SAFETY: The buffer always contains valid UTF-8.
141 unsafe { str::from_utf8_unchecked(self.as_bytes()) }
142 }
143
144 /// Returns a mutable reference to the underlying bytes casted to a string
145 /// slice.
146 #[inline]
147 pub fn as_mut_str(&mut self) -> &mut str {
148 // SAFETY: The buffer always contains valid UTF-8.
149 unsafe { str::from_utf8_unchecked_mut(self.as_mut_bytes()) }
150 }
151
152 /// Copies `self` into a new `Vec`.
153 #[cfg(feature = "alloc")]
154 #[inline]
155 pub fn to_vec(&self) -> Vec<u8> {
156 self.as_bytes().to_vec()
157 }
158
159 /// Returns a reference the underlying stack-allocated byte array.
160 ///
161 /// # Panics
162 ///
163 /// If `LEN` does not equal `Self::LEN`.
164 ///
165 /// This is panic is evaluated at compile-time if the `nightly` feature
166 /// is enabled, as inline `const` blocks are currently unstable.
167 ///
168 /// See Rust tracking issue [#76001](https://github.com/rust-lang/rust/issues/76001).
169 #[inline]
170 pub const fn as_byte_array<const LEN: usize>(&self) -> &[u8; LEN] {
171 maybe_const_assert!(LEN == Self::LEN, "`LEN` must be equal to `Self::LEN`");
172 // SAFETY: [u16; N] is layout-compatible with [u8; N * 2].
173 unsafe { &*self.as_ptr().cast::<[u8; LEN]>() }
174 }
175
176 /// Returns a mutable reference the underlying stack-allocated byte array.
177 ///
178 /// # Panics
179 ///
180 /// If `LEN` does not equal `Self::LEN`.
181 ///
182 /// See [`as_byte_array`](Buffer::as_byte_array) for more information.
183 #[inline]
184 pub fn as_mut_byte_array<const LEN: usize>(&mut self) -> &mut [u8; LEN] {
185 maybe_const_assert!(LEN == Self::LEN, "`LEN` must be equal to `Self::LEN`");
186 // SAFETY: [u16; N] is layout-compatible with [u8; N * 2].
187 unsafe { &mut *self.as_mut_ptr().cast::<[u8; LEN]>() }
188 }
189
190 /// Returns a reference to the underlying bytes.
191 #[inline]
192 pub const fn as_bytes(&self) -> &[u8] {
193 // SAFETY: [u16; N] is layout-compatible with [u8; N * 2].
194 unsafe { slice::from_raw_parts(self.as_ptr(), Self::LEN) }
195 }
196
197 /// Returns a mutable reference to the underlying bytes.
198 ///
199 /// # Safety
200 ///
201 /// The caller must ensure that the content of the slice is valid UTF-8
202 /// before the borrow ends and the underlying `str` is used.
203 ///
204 /// Use of a `str` whose contents are not valid UTF-8 is undefined behavior.
205 #[inline]
206 pub unsafe fn as_mut_bytes(&mut self) -> &mut [u8] {
207 // SAFETY: [u16; N] is layout-compatible with [u8; N * 2].
208 unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), Self::LEN) }
209 }
210
211 /// Returns a mutable reference to the underlying buffer, excluding the prefix.
212 ///
213 /// # Safety
214 ///
215 /// See [`as_mut_bytes`](Buffer::as_mut_bytes).
216 #[inline]
217 pub unsafe fn buffer(&mut self) -> &mut [u8] {
218 unsafe { slice::from_raw_parts_mut(self.bytes.as_mut_ptr().cast(), N * 2) }
219 }
220
221 /// Returns a raw pointer to the buffer.
222 ///
223 /// The caller must ensure that the buffer outlives the pointer this
224 /// function returns, or else it will end up pointing to garbage.
225 #[inline]
226 pub const fn as_ptr(&self) -> *const u8 {
227 unsafe { (self as *const Self).cast::<u8>().add(!PREFIX as usize * 2) }
228 }
229
230 /// Returns an unsafe mutable pointer to the slice's buffer.
231 ///
232 /// The caller must ensure that the slice outlives the pointer this
233 /// function returns, or else it will end up pointing to garbage.
234 #[inline]
235 pub fn as_mut_ptr(&mut self) -> *mut u8 {
236 unsafe { (self as *mut Self).cast::<u8>().add(!PREFIX as usize * 2) }
237 }
238}