| 1 | //! A buffer for constructing a string while avoiding heap allocation. |
| 2 | |
| 3 | use core::hash::{Hash, Hasher}; |
| 4 | use core::mem::MaybeUninit; |
| 5 | use core::{fmt, str}; |
| 6 | |
| 7 | use crate::smart_display::{FormatterOptions, Metadata, SmartDisplay}; |
| 8 | |
| 9 | /// A buffer for construct a string while avoiding heap allocation. |
| 10 | /// |
| 11 | /// The only requirement is that the buffer is large enough to hold the formatted string. |
| 12 | pub struct WriteBuffer<const SIZE: usize> { |
| 13 | buf: [MaybeUninit<u8>; SIZE], |
| 14 | len: usize, |
| 15 | } |
| 16 | |
| 17 | impl<const SIZE: usize> fmt::Debug for WriteBuffer<SIZE> { |
| 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 19 | f&mut DebugStruct<'_, '_>.debug_struct("DisplayBuffer" ) |
| 20 | .field("buf" , &self.as_str()) |
| 21 | .field(name:"remaining_capacity" , &self.remaining_capacity()) |
| 22 | .finish() |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | impl<const SIZE: usize> WriteBuffer<SIZE> { |
| 27 | /// Creates an empty buffer. |
| 28 | pub const fn new() -> Self { |
| 29 | Self { |
| 30 | buf: maybe_uninit_uninit_array::<_, SIZE>(), |
| 31 | len: 0, |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | /// Obtain the contents of the buffer as a string. |
| 36 | pub fn as_str(&self) -> &str { |
| 37 | self |
| 38 | } |
| 39 | |
| 40 | /// Determine how many bytes are remaining in the buffer. |
| 41 | pub const fn remaining_capacity(&self) -> usize { |
| 42 | SIZE - self.len |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | impl<const SIZE: usize> Default for WriteBuffer<SIZE> { |
| 47 | fn default() -> Self { |
| 48 | Self::new() |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | impl<const LEFT_SIZE: usize, const RIGHT_SIZE: usize> PartialOrd<WriteBuffer<RIGHT_SIZE>> |
| 53 | for WriteBuffer<LEFT_SIZE> |
| 54 | { |
| 55 | fn partial_cmp(&self, other: &WriteBuffer<RIGHT_SIZE>) -> Option<core::cmp::Ordering> { |
| 56 | self.as_str().partial_cmp(other.as_str()) |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | impl<const LEFT_SIZE: usize, const RIGHT_SIZE: usize> PartialEq<WriteBuffer<RIGHT_SIZE>> |
| 61 | for WriteBuffer<LEFT_SIZE> |
| 62 | { |
| 63 | fn eq(&self, other: &WriteBuffer<RIGHT_SIZE>) -> bool { |
| 64 | self.as_str() == other.as_str() |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | impl<const SIZE: usize> Eq for WriteBuffer<SIZE> {} |
| 69 | |
| 70 | impl<const SIZE: usize> Ord for WriteBuffer<SIZE> { |
| 71 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { |
| 72 | self.as_str().cmp(other.as_str()) |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | impl<const SIZE: usize> Hash for WriteBuffer<SIZE> { |
| 77 | fn hash<H: Hasher>(&self, state: &mut H) { |
| 78 | self.as_str().hash(state) |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | impl<const SIZE: usize> AsRef<str> for WriteBuffer<SIZE> { |
| 83 | fn as_ref(&self) -> &str { |
| 84 | self |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | impl<const SIZE: usize> AsRef<[u8]> for WriteBuffer<SIZE> { |
| 89 | fn as_ref(&self) -> &[u8] { |
| 90 | self.as_bytes() |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | impl<const SIZE: usize> core::borrow::Borrow<str> for WriteBuffer<SIZE> { |
| 95 | fn borrow(&self) -> &str { |
| 96 | self |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | impl<const SIZE: usize> core::ops::Deref for WriteBuffer<SIZE> { |
| 101 | type Target = str; |
| 102 | |
| 103 | fn deref(&self) -> &Self::Target { |
| 104 | // SAFETY: `buf` is only written to by the `fmt::Write::write_str` implementation which |
| 105 | // writes a valid UTF-8 string to `buf` and correctly sets `len`. |
| 106 | unsafe { |
| 107 | let s: &[u8] = maybe_uninit_slice_assume_init_ref(&self.buf[..self.len]); |
| 108 | str::from_utf8_unchecked(s) |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | impl<const SIZE: usize> fmt::Display for WriteBuffer<SIZE> { |
| 114 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 115 | f.write_str(self) |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | impl<const SIZE: usize> SmartDisplay for WriteBuffer<SIZE> { |
| 120 | type Metadata = (); |
| 121 | |
| 122 | fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> { |
| 123 | Metadata::new(self.len, self, ()) |
| 124 | } |
| 125 | |
| 126 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 127 | f.pad(self) |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | impl<const SIZE: usize> fmt::Write for WriteBuffer<SIZE> { |
| 132 | fn write_str(&mut self, s: &str) -> fmt::Result { |
| 133 | let bytes: &[u8] = s.as_bytes(); |
| 134 | |
| 135 | if let Some(buf: &mut [MaybeUninit]) = self.buf.get_mut(self.len..(self.len + bytes.len())) { |
| 136 | maybe_uninit_write_slice(this:buf, src:bytes); |
| 137 | self.len += bytes.len(); |
| 138 | Ok(()) |
| 139 | } else { |
| 140 | Err(fmt::Error) |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | /// Equivalent of [`MaybeUninit::uninit_array`] that compiles on stable. |
| 146 | #[must_use ] |
| 147 | #[inline (always)] |
| 148 | const fn maybe_uninit_uninit_array<T, const N: usize>() -> [MaybeUninit<T>; N] { |
| 149 | // SAFETY: An uninitialized `[MaybeUninit<_>; LEN]` is valid. |
| 150 | unsafe { MaybeUninit::<[MaybeUninit<T>; N]>::uninit().assume_init() } |
| 151 | } |
| 152 | |
| 153 | /// Equivalent of [`MaybeUninit::write_slice`] that compiles on stable. |
| 154 | fn maybe_uninit_write_slice<'a, T>(this: &'a mut [MaybeUninit<T>], src: &[T]) -> &'a mut [T] |
| 155 | where |
| 156 | T: Copy, |
| 157 | { |
| 158 | #[allow (trivial_casts)] |
| 159 | // SAFETY: T and MaybeUninit<T> have the same layout |
| 160 | let uninit_src: &[MaybeUninit] = unsafe { &*(src as *const [T] as *const [MaybeUninit<T>]) }; |
| 161 | |
| 162 | this.copy_from_slice(uninit_src); |
| 163 | |
| 164 | // SAFETY: Valid elements have just been copied into `this` so it is initialized |
| 165 | unsafe { maybe_uninit_slice_assume_init_mut(slice:this) } |
| 166 | } |
| 167 | |
| 168 | /// Equivalent of [`MaybeUninit::slice_assume_init_mut`] that compiles on stable. |
| 169 | /// |
| 170 | /// # Safety |
| 171 | /// |
| 172 | /// See [`MaybeUninit::slice_assume_init_mut`](https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#method.slice_assume_init_mut). |
| 173 | #[inline (always)] |
| 174 | unsafe fn maybe_uninit_slice_assume_init_mut<T, U>(slice: &mut [MaybeUninit<T>]) -> &mut [U] { |
| 175 | #[allow (trivial_casts)] |
| 176 | // SAFETY: similar to safety notes for `slice_get_ref`, but we have a mutable reference which is |
| 177 | // also guaranteed to be valid for writes. |
| 178 | unsafe { |
| 179 | &mut *(slice as *mut [MaybeUninit<T>] as *mut [U]) |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /// Equivalent of [`MaybeUninit::slice_assume_init_ref`] that compiles on stable. |
| 184 | /// |
| 185 | /// # Safety |
| 186 | /// |
| 187 | /// See [`MaybeUninit::slice_assume_init_ref`](https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#method.slice_assume_init_ref). |
| 188 | #[inline (always)] |
| 189 | const unsafe fn maybe_uninit_slice_assume_init_ref<T>(slice: &[MaybeUninit<T>]) -> &[T] { |
| 190 | #[allow (trivial_casts)] |
| 191 | // SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that `slice` is |
| 192 | // initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`. The pointer |
| 193 | // obtained is valid since it refers to memory owned by `slice` which is a reference and thus |
| 194 | // guaranteed to be valid for reads. |
| 195 | unsafe { |
| 196 | &*(slice as *const [MaybeUninit<T>] as *const [T]) |
| 197 | } |
| 198 | } |
| 199 | |