Skip to main content

powerfmt/
buf.rs

1//! A buffer for constructing a string while avoiding heap allocation.
2
3use core::hash::{Hash, Hasher};
4use core::mem::MaybeUninit;
5use core::{fmt, slice, str};
6
7use 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.
12pub struct WriteBuffer<const SIZE: usize> {
13    buf: [MaybeUninit<u8>; SIZE],
14    len: usize,
15}
16
17impl<const SIZE: usize> fmt::Debug for WriteBuffer<SIZE> {
18    #[inline]
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        f.debug_struct("DisplayBuffer")
21            .field("buf", &self.as_str())
22            .field("remaining_capacity", &self.remaining_capacity())
23            .finish()
24    }
25}
26
27impl<const SIZE: usize> WriteBuffer<SIZE> {
28    /// Creates an empty buffer.
29    #[inline]
30    pub const fn new() -> Self {
31        Self {
32            buf: [MaybeUninit::uninit(); SIZE],
33            len: 0,
34        }
35    }
36
37    /// Obtain the contents of the buffer as a string.
38    #[inline]
39    pub fn as_str(&self) -> &str {
40        self
41    }
42
43    /// Determine how many bytes are remaining in the buffer.
44    #[inline]
45    pub const fn remaining_capacity(&self) -> usize {
46        SIZE - self.len
47    }
48}
49
50impl<const SIZE: usize> Default for WriteBuffer<SIZE> {
51    #[inline]
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl<const LEFT_SIZE: usize, const RIGHT_SIZE: usize> PartialOrd<WriteBuffer<RIGHT_SIZE>>
58    for WriteBuffer<LEFT_SIZE>
59{
60    #[inline]
61    fn partial_cmp(&self, other: &WriteBuffer<RIGHT_SIZE>) -> Option<core::cmp::Ordering> {
62        self.as_str().partial_cmp(other.as_str())
63    }
64}
65
66impl<const LEFT_SIZE: usize, const RIGHT_SIZE: usize> PartialEq<WriteBuffer<RIGHT_SIZE>>
67    for WriteBuffer<LEFT_SIZE>
68{
69    #[inline]
70    fn eq(&self, other: &WriteBuffer<RIGHT_SIZE>) -> bool {
71        self.as_str() == other.as_str()
72    }
73}
74
75impl<const SIZE: usize> Eq for WriteBuffer<SIZE> {}
76
77impl<const SIZE: usize> Ord for WriteBuffer<SIZE> {
78    #[inline]
79    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
80        self.as_str().cmp(other.as_str())
81    }
82}
83
84impl<const SIZE: usize> Hash for WriteBuffer<SIZE> {
85    #[inline]
86    fn hash<H: Hasher>(&self, state: &mut H) {
87        self.as_str().hash(state)
88    }
89}
90
91impl<const SIZE: usize> AsRef<str> for WriteBuffer<SIZE> {
92    #[inline]
93    fn as_ref(&self) -> &str {
94        self
95    }
96}
97
98impl<const SIZE: usize> AsRef<[u8]> for WriteBuffer<SIZE> {
99    #[inline]
100    fn as_ref(&self) -> &[u8] {
101        self.as_bytes()
102    }
103}
104
105impl<const SIZE: usize> core::borrow::Borrow<str> for WriteBuffer<SIZE> {
106    #[inline]
107    fn borrow(&self) -> &str {
108        self
109    }
110}
111
112impl<const SIZE: usize> core::ops::Deref for WriteBuffer<SIZE> {
113    type Target = str;
114
115    #[inline]
116    fn deref(&self) -> &Self::Target {
117        // Safety: `MaybeUninit<T>` and `T` have the same layout. `buf` is only written to by the
118        // `fmt::Write::write_str` implementation which writes a valid UTF-8 string to `buf`
119        // and correctly sets `len`. All other safety requirements are upheld
120        // by the original slice.
121        unsafe {
122            let slice = self.buf.get_unchecked(..self.len);
123            let slice = slice::from_raw_parts(slice.as_ptr().cast(), slice.len());
124            str::from_utf8_unchecked(slice)
125        }
126    }
127}
128
129impl<const SIZE: usize> fmt::Display for WriteBuffer<SIZE> {
130    #[inline]
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        f.write_str(self)
133    }
134}
135
136impl<const SIZE: usize> SmartDisplay for WriteBuffer<SIZE> {
137    type Metadata = ();
138
139    #[inline]
140    fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> {
141        Metadata::new(self.len, self, ())
142    }
143
144    #[inline]
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        f.pad(self)
147    }
148}
149
150impl<const SIZE: usize> fmt::Write for WriteBuffer<SIZE> {
151    #[inline]
152    fn write_str(&mut self, s: &str) -> fmt::Result {
153        let bytes = s.as_bytes();
154
155        if let Some(buf) = self.buf.get_mut(self.len..(self.len + bytes.len())) {
156            maybe_uninit_write_slice(buf, bytes);
157            self.len += bytes.len();
158            Ok(())
159        } else {
160            Err(fmt::Error)
161        }
162    }
163}
164
165/// Equivalent of [`MaybeUninit::write_slice`] that compiles on stable.
166fn maybe_uninit_write_slice<'a, T>(this: &'a mut [MaybeUninit<T>], src: &[T]) -> &'a mut [T]
167where
168    T: Copy,
169{
170    #[allow(trivial_casts)]
171    // Safety: `T` and `MaybeUninit<T>` have the same layout
172    let uninit_src = unsafe { &*(src as *const [T] as *const [MaybeUninit<T>]) };
173
174    this.copy_from_slice(uninit_src);
175
176    // Safety: `MaybeUninit<T>` and `T` have the same layout. Valid elements have just been copied
177    // into `this` so it is initialized. All other safety requirements are upheld by the
178    // original slice.
179    unsafe { slice::from_raw_parts_mut(this.as_mut_ptr().cast(), this.len()) }
180}