str_buf/
lib.rs

1//!Static string buffer
2//!
3//!Features:
4//!
5//!- `serde` Enables serde serialization. In case of overflow, deserialize fails.
6//!- `ufmt-write` Enables ufmt `uWrite` implementation.
7#![warn(missing_docs)]
8
9#![no_std]
10#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
11#![cfg_attr(rustfmt, rustfmt_skip)]
12
13use core::{mem, slice, ptr, cmp, ops, hash, fmt, borrow};
14
15#[cfg(feature = "serde")]
16mod serde;
17#[cfg(feature = "ufmt-write")]
18mod ufmt;
19
20#[derive(Debug, Clone)]
21///`StrBuf` conversion error
22pub enum StrBufError {
23    ///Not enough space for string to be converted into `StrBuf`.
24    Overflow,
25}
26
27impl fmt::Display for StrBufError {
28    #[inline(always)]
29    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            StrBufError::Overflow => fmt.write_str("Buffer overflow"),
32        }
33    }
34}
35
36#[derive(Copy, Clone)]
37///Stack based string.
38///
39///It's size is `mem::size_of::<T>() + mem::size_of::<u8>()`, but remember that it can be padded.
40///Can store up to `u8::max_value()` as anything bigger makes it impractical.
41///
42///Storage is always capped at `u8::max_value()`, which practically means panic during creation,
43///until compiler provides a better means to error.
44///
45///When attempting to create new instance from `&str` it panics on overflow in debug mode.
46///
47///```
48///use str_buf::StrBuf;
49///use core::mem;
50///use core::fmt::Write;
51///use core::convert::TryInto;
52///
53///type MyStr = StrBuf::<{mem::size_of::<String>()}>;
54///
55///const CONST_STR: MyStr = MyStr::new().and("hello").and(" ").and("world");
56///
57///assert_eq!(CONST_STR, "hello world");
58///
59///assert_eq!(MyStr::capacity(), mem::size_of::<String>());
60/////If you want it to be equal to string you'll have to adjust storage accordingly
61///assert_ne!(mem::size_of::<MyStr>(), mem::size_of::<String>());
62///assert_eq!(mem::size_of::<StrBuf::<{mem::size_of::<String>() - 1}>>(), mem::size_of::<String>());
63///
64///let text: MyStr = "test".try_into().expect("To fit string");
65///assert_eq!("test", text);
66///assert_eq!(text, "test");
67///let mut text = MyStr::new();
68///let _ = write!(text, "test {}", "hello world");
69///assert_eq!(text.as_str(), "test hello world");
70///assert_eq!(text.remaining(), MyStr::capacity() - "test hello world".len());
71///
72///assert_eq!(text.push_str(" or maybe not"), 8); //Overflow!
73///assert_eq!(text.as_str(), "test hello world or mayb");
74///assert_eq!(text.push_str(" or maybe not"), 0); //Overflow, damn
75///
76///text.clear();
77///assert_eq!(text.push_str(" or maybe not"), 13); //noice
78///assert_eq!(text.as_str(), " or maybe not");
79///
80///assert_eq!(text.clone().as_str(), text.as_str());
81///assert_eq!(text.clone(), text);
82///```
83pub struct StrBuf<const N: usize> {
84    inner: [mem::MaybeUninit<u8>; N],
85    cursor: u8, //number of bytes written
86}
87
88impl<const N: usize> StrBuf<N> {
89    #[inline]
90    ///Creates new instance
91    pub const fn new() -> Self {
92        unsafe {
93            Self::from_storage([mem::MaybeUninit::uninit(); N], 0)
94        }
95    }
96
97    #[inline]
98    ///Creates new instance from supplied storage and written size.
99    ///
100    ///It is unsafe, because there is no guarantee that storage is correctly initialized with UTF-8
101    ///bytes.
102    pub const unsafe fn from_storage(storage: [mem::MaybeUninit<u8>; N], cursor: u8) -> Self {
103        debug_assert!(N <= u8::max_value() as usize, "Capacity cannot be more than 255");
104
105        Self {
106            inner: storage,
107            cursor,
108        }
109    }
110
111    #[inline]
112    ///Creates new instance from existing slice with panic on overflow
113    pub const fn from_str(text: &str) -> Self {
114        let mut idx = 0;
115        let mut storage = [mem::MaybeUninit::<u8>::uninit(); N];
116
117        debug_assert!(text.len() <= storage.len(), "Text cannot fit static storage");
118        while idx < text.len() {
119            storage[idx] = mem::MaybeUninit::new(text.as_bytes()[idx]);
120            idx += 1;
121        }
122
123        unsafe {
124            Self::from_storage(storage, idx as u8)
125        }
126    }
127
128    #[inline]
129    ///Creates new instance from existing slice which returns error on overflow
130    pub const fn from_str_checked(text: &str) -> Result<Self, StrBufError> {
131        if text.len() <= Self::capacity() {
132            Ok(Self::from_str(text))
133        } else {
134            Err(StrBufError::Overflow)
135        }
136    }
137
138    #[inline(always)]
139    ///Reads byte at `idx`.
140    pub const unsafe fn get_unchecked(&self, idx: usize) -> u8 {
141        self.inner[idx].assume_init()
142    }
143
144    #[inline]
145    ///Reads byte at `idx`.
146    pub const fn get(&self, idx: usize) -> Option<u8> {
147        if idx < self.cursor as usize {
148            unsafe {
149                Some(self.get_unchecked(idx))
150            }
151        } else {
152            None
153        }
154    }
155
156    #[inline]
157    ///Returns pointer  to the beginning of underlying buffer
158    pub const fn as_ptr(&self) -> *const u8 {
159        self.inner.as_ptr() as _
160    }
161
162    #[inline]
163    ///Returns pointer  to the beginning of underlying buffer
164    pub fn as_mut_ptr(&mut self) -> *mut u8 {
165        self.inner.as_mut_ptr() as *mut u8
166    }
167
168    #[inline]
169    ///Returns number of bytes left (not written yet)
170    pub const fn remaining(&self) -> usize {
171        Self::capacity() - self.cursor as usize
172    }
173
174    #[inline]
175    ///Returns reference to underlying storage as it is.
176    pub const fn as_storage(&self) -> &[mem::MaybeUninit<u8>; N] {
177        &self.inner
178    }
179
180    #[inline]
181    ///Returns reference to underlying storage as it is.
182    ///
183    ///To safely modify the storage, user must guarantee to write valid UTF-8
184    pub unsafe fn as_mut_storage(&mut self) -> &mut [mem::MaybeUninit<u8>; N] {
185        &mut self.inner
186    }
187
188    #[inline]
189    ///Returns slice to already written data.
190    pub const fn as_slice(&self) -> &[u8] {
191        //Layout is: (<ptr>, <usize>)
192        //
193        //Reference:
194        //https://github.com/rust-lang/rust/blob/6830052c7b87217886324129bffbe096e485d415/library/core/src/ptr/metadata.rs#L145=
195        #[repr(C)]
196        struct RawSlice {
197            ptr: *const u8,
198            size: usize,
199        }
200
201        debug_assert!(unsafe {
202            mem::transmute::<_, RawSlice>([3, 2, 1].as_slice()).size
203        } == 3, "RawSlice layout has been changed in compiler unexpectedly");
204
205        unsafe {
206            mem::transmute(RawSlice {
207                ptr: self.as_ptr(),
208                size: self.len(),
209            })
210        }
211    }
212
213    #[inline]
214    ///Returns mutable slice to already written data.
215    ///
216    ///To safely modify the slice, user must guarantee to write valid UTF-8
217    pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
218        slice::from_raw_parts_mut(self.as_mut_ptr(), self.cursor as usize)
219    }
220
221    #[inline]
222    ///Returns mutable slice with unwritten parts of the buffer.
223    pub fn as_write_slice(&mut self) -> &mut [mem::MaybeUninit<u8>] {
224        &mut self.inner[self.cursor as usize..]
225    }
226
227    #[inline(always)]
228    ///Clears the content of buffer.
229    pub fn clear(&mut self) {
230        unsafe {
231            self.set_len(0);
232        }
233    }
234
235    #[inline(always)]
236    ///Returns empty self.
237    pub const fn empty(mut self) -> Self {
238        self.cursor = 0;
239        self
240    }
241
242    #[inline]
243    ///Shortens the buffer, keeping the first `cursor` elements.
244    ///
245    ///Does nothing if new `cursor` is after current position.
246    ///
247    ///Unsafe as it is up to user to consider character boundary
248    pub unsafe fn truncate(&mut self, cursor: u8) {
249        if cursor < self.cursor {
250            self.set_len(cursor);
251        }
252    }
253
254    #[inline]
255    ///Returns buffer overall capacity.
256    pub const fn capacity() -> usize {
257        if N > u8::max_value() as usize {
258            u8::max_value() as usize
259        } else {
260            N
261        }
262    }
263
264    #[inline]
265    ///Returns number of bytes written.
266    pub const fn len(&self) -> usize {
267        self.cursor as usize
268    }
269
270    #[inline(always)]
271    ///Sets new length of the string.
272    pub unsafe fn set_len(&mut self, len: u8) {
273        self.cursor = len
274    }
275
276    #[inline]
277    ///Appends given string without any size checks
278    pub unsafe fn push_str_unchecked(&mut self, text: &str) {
279        ptr::copy_nonoverlapping(text.as_ptr(), self.as_mut_ptr().offset(self.cursor as isize), text.len());
280        self.set_len(self.cursor.saturating_add(text.len() as u8));
281    }
282
283    #[inline]
284    ///Appends given string, truncating on overflow, returning number of written bytes
285    pub fn push_str(&mut self, text: &str) -> usize {
286        let mut size = cmp::min(text.len(), self.remaining());
287
288        #[cold]
289        fn shift_by_char_boundary(text: &str, mut size: usize) -> usize {
290            while !text.is_char_boundary(size) {
291                size -= 1;
292            }
293            size
294        }
295
296        if !text.is_char_boundary(size) {
297            //0 is always char boundary so 0 - 1 is impossible
298            size = shift_by_char_boundary(text, size - 1);
299        }
300
301        unsafe {
302            self.push_str_unchecked(&text[..size]);
303        }
304        size
305    }
306
307    #[inline]
308    ///Appends given string, assuming it fits.
309    ///
310    ///On overflow panics with index out of bounds.
311    pub const fn and(self, text: &str) -> Self {
312        unsafe {
313            self.and_unsafe(text.as_bytes())
314        }
315    }
316
317   #[inline]
318    ///Unsafely appends given bytes, assuming valid utf-8.
319    ///
320    ///On overflow panics with index out of bounds as `and`.
321    pub const unsafe fn and_unsafe(mut self, bytes: &[u8]) -> Self {
322        debug_assert!(self.remaining() >= bytes.len(), "Buffer overflow");
323
324        let mut idx = 0;
325        while idx < bytes.len() {
326            let cursor = self.cursor as usize + idx;
327            self.inner[cursor] = mem::MaybeUninit::new(bytes[idx]);
328            idx += 1;
329        }
330        self.cursor = self.cursor.saturating_add(bytes.len() as u8);
331
332        self
333    }
334
335    #[inline(always)]
336    ///Access str from underlying storage
337    ///
338    ///Returns empty if nothing has been written into buffer yet.
339    pub const fn as_str(&self) -> &str {
340        //You think I care?
341        //Make `from_utf8_unchecked` const fn first
342        unsafe {
343            core::str::from_utf8_unchecked(self.as_slice())
344        }
345    }
346
347    #[inline(always)]
348    ///Converts this string to its ASCII lower case equivalent in-place.
349    pub fn make_ascii_lowercase(&mut self) {
350        unsafe {
351            self.as_mut_slice().make_ascii_lowercase()
352        }
353    }
354
355    #[inline(always)]
356    ///Converts this string to its ASCII upper case equivalent in-place.
357    pub fn make_ascii_uppercase(&mut self) {
358        unsafe {
359            self.as_mut_slice().make_ascii_uppercase()
360        }
361    }
362}
363
364impl<const S: usize> AsRef<str> for StrBuf<S> {
365    #[inline(always)]
366    fn as_ref(&self) -> &str {
367        self.as_str()
368    }
369}
370
371impl<const S: usize> core::fmt::Write for StrBuf<S> {
372    #[inline(always)]
373    fn write_str(&mut self, s: &str) -> core::fmt::Result {
374        if self.push_str(s) == s.len() {
375            Ok(())
376        } else {
377            Err(core::fmt::Error)
378        }
379    }
380}
381
382impl<const S: usize> core::fmt::Display for StrBuf<S> {
383    #[inline(always)]
384    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
385        f.write_str(self.as_str())
386    }
387}
388
389impl<const S: usize> core::fmt::Debug for StrBuf<S> {
390    #[inline(always)]
391    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
392        f.write_str(self.as_str())
393    }
394}
395
396impl<const S: usize> AsRef<[u8]> for StrBuf<S> {
397    #[inline(always)]
398    fn as_ref(&self) -> &[u8] {
399        self.as_slice()
400    }
401}
402
403impl<const S: usize> borrow::Borrow<str> for StrBuf<S> {
404    #[inline(always)]
405    fn borrow(&self) -> &str {
406        self.as_str()
407    }
408}
409
410impl<const S: usize> ops::Deref for StrBuf<S> {
411    type Target = str;
412
413    #[inline(always)]
414    fn deref(&self) -> &str {
415        self.as_str()
416    }
417}
418
419impl<const S: usize> Eq for StrBuf<S> {}
420
421impl<const S: usize> PartialEq<StrBuf<S>> for StrBuf<S> {
422    #[inline(always)]
423    fn eq(&self, other: &Self) -> bool {
424        self.as_str() == other.as_str()
425    }
426}
427
428impl<const S: usize> PartialEq<StrBuf<S>> for &str {
429    #[inline(always)]
430    fn eq(&self, other: &StrBuf<S>) -> bool {
431        *self == other.as_str()
432    }
433}
434
435impl<const S: usize> PartialEq<StrBuf<S>> for str {
436    #[inline(always)]
437    fn eq(&self, other: &StrBuf<S>) -> bool {
438        self == other.as_str()
439    }
440}
441
442impl<const S: usize> PartialEq<str> for StrBuf<S> {
443    #[inline(always)]
444    fn eq(&self, other: &str) -> bool {
445        self.as_str() == other
446    }
447}
448
449impl<const S: usize> PartialEq<&str> for StrBuf<S> {
450    #[inline(always)]
451    fn eq(&self, other: &&str) -> bool {
452        self.as_str() == *other
453    }
454}
455
456impl<const S: usize> cmp::Ord for StrBuf<S> {
457    fn cmp(&self, other: &Self) -> cmp::Ordering {
458        self.as_str().cmp(other.as_str())
459    }
460}
461
462impl<const S: usize> PartialOrd for StrBuf<S> {
463    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
464        Some(self.cmp(other))
465    }
466}
467
468impl<const S: usize> hash::Hash for StrBuf<S> {
469    fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
470        self.as_str().hash(hasher)
471    }
472}
473
474impl<const S: usize> core::convert::TryFrom<&str> for StrBuf<S> {
475    type Error = StrBufError;
476
477    #[inline(always)]
478    fn try_from(text: &str) -> Result<Self, Self::Error> {
479        Self::from_str_checked(text)
480    }
481}
482
483impl<const S: usize> core::str::FromStr for StrBuf<S> {
484    type Err = StrBufError;
485
486    #[inline(always)]
487    fn from_str(text: &str) -> Result<Self, Self::Err> {
488        Self::from_str_checked(text)
489    }
490}