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#![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///Max capacity to use length of size 1 byte
37pub const CAPACITY_U8: usize = 256;
38///Max capacity to use length of size 2 byte
39pub const CAPACITY_U16: usize = 65537;
40
41///Calculates necessary buffer capacity to fit provided number of bytes
42pub const fn capacity(desired: usize) -> usize {
43    if desired == 0 {
44        desired
45    } else if desired <= u8::MAX as usize {
46        desired.saturating_add(1)
47    } else if desired <= u16::MAX as usize {
48        desired.saturating_add(2)
49    } else {
50        desired.saturating_add(mem::size_of::<usize>())
51    }
52}
53
54#[repr(transparent)]
55#[derive(Copy, Clone)]
56///Stack based string.
57///
58///It's size is `mem::size_of::<[u8; T]>()` including bytes for length of the string
59///
60///Depending on size of string, it uses different length size:
61///- `0` - Uses 0 bytes to store length
62///- `1..=256` - Uses 1 byte to store length
63///- `257..=65537` - Uses 2 bytes to store length
64///- `65537..` - Uses `mem::size_of::<usize>()` bytes to store length
65///
66///In case of capacity overflow there is no re-adjustment possible
67///Therefore When attempting to create new instance from `&str` it panics on overflow.
68///
69///```
70///use str_buf::StrBuf;
71///use core::mem;
72///use core::fmt::Write;
73///use core::convert::TryInto;
74///
75///type MyStr = StrBuf::<{str_buf::capacity(mem::size_of::<String>())}>;
76///
77///const CONST_STR: MyStr = MyStr::new().and("hello").and(" ").and("world");
78///
79///assert_eq!(CONST_STR.len(), "hello world".len(), "Length should be 11 characters");
80///assert_eq!(CONST_STR, "hello world");
81///
82///assert_eq!(MyStr::capacity(), mem::size_of::<String>(), "Should be equal to size of String");
83///
84///let text: MyStr = "test".try_into().expect("To fit string");
85///assert_eq!("test", text);
86///assert_eq!(text, "test");
87///let mut text = MyStr::new();
88///let _ = write!(text, "test {}", "hello world");
89///assert_eq!(text.as_str(), "test hello world");
90///assert_eq!(text.remaining(), MyStr::capacity() - "test hello world".len(), "Should modify length");
91///
92///assert_eq!(text.push_str(" or maybe not"), 8); //Overflow!
93///assert_eq!(text.as_str(), "test hello world or mayb");
94///assert_eq!(text.push_str(" or maybe not"), 0); //Overflow, damn
95///
96///text.clear();
97///assert_eq!(text.push_str(" or maybe not"), 13); //noice
98///assert_eq!(text.as_str(), " or maybe not");
99///
100///assert_eq!(text.clone().as_str(), text.as_str());
101///assert_eq!(text.clone(), text);
102///```
103pub struct StrBuf<const N: usize> {
104    inner: [mem::MaybeUninit<u8>; N],
105}
106
107impl<const N: usize> StrBuf<N> {
108    ///Length of bytes used to store buffer's length
109    pub const LEN_OFFSET: usize = if N == 0 {
110        0
111    } else if N <= CAPACITY_U8 {
112        1
113    } else if N <= CAPACITY_U16 {
114        2
115    } else {
116        mem::size_of::<usize>()
117    };
118
119    const CAPACITY: usize = N - Self::LEN_OFFSET;
120
121    #[inline]
122    ///Creates new instance
123    pub const fn new() -> Self {
124        unsafe {
125            Self::from_storage([mem::MaybeUninit::uninit(); N]).const_set_len(0)
126        }
127    }
128
129    #[inline]
130    ///Creates new instance from supplied storage and written size.
131    ///
132    ///It is unsafe, because there is no guarantee that storage is correctly initialized with UTF-8
133    ///bytes.
134    ///
135    ///First `Self::LEN_OFFSET` must be initialized with its length
136    pub const unsafe fn from_storage(storage: [mem::MaybeUninit<u8>; N]) -> Self {
137        debug_assert!(N <= usize::max_value(), "Capacity cannot exceed usize");
138
139        Self {
140            inner: storage,
141        }
142    }
143
144    #[inline]
145    ///Creates new instance from existing slice with panic on overflow
146    pub const fn from_str(text: &str) -> Self {
147        let mut idx = 0;
148        let mut storage = [mem::MaybeUninit::<u8>::uninit(); N];
149
150        debug_assert!(text.len() <= Self::CAPACITY, "Text cannot fit static storage");
151        while idx < text.len() {
152            storage[Self::LEN_OFFSET + idx] = mem::MaybeUninit::new(text.as_bytes()[idx]);
153            idx += 1;
154        }
155
156        unsafe {
157            Self::from_storage(storage).const_set_len(text.len())
158        }
159    }
160
161    #[inline]
162    ///Creates new instance from existing slice which returns error on overflow
163    pub const fn from_str_checked(text: &str) -> Result<Self, StrBufError> {
164        if text.len() <= Self::capacity() {
165            Ok(Self::from_str(text))
166        } else {
167            Err(StrBufError::Overflow)
168        }
169    }
170
171    #[inline(always)]
172    ///Reads byte at `idx`.
173    pub const unsafe fn get_unchecked(&self, idx: usize) -> u8 {
174        self.inner[Self::LEN_OFFSET + idx].assume_init()
175    }
176
177    #[inline]
178    ///Reads byte at `idx`.
179    pub const fn get(&self, idx: usize) -> Option<u8> {
180        if idx < self.len() {
181            unsafe {
182                Some(self.get_unchecked(idx))
183            }
184        } else {
185            None
186        }
187    }
188
189    #[inline]
190    ///Returns pointer  to the beginning of underlying buffer
191    pub const fn as_ptr(&self) -> *const u8 {
192        unsafe {
193            self.inner.as_ptr().add(Self::LEN_OFFSET) as _
194        }
195    }
196
197    #[inline]
198    ///Returns pointer  to the beginning of underlying buffer
199    pub fn as_mut_ptr(&mut self) -> *mut u8 {
200        unsafe {
201            self.inner.as_mut_ptr().add(Self::LEN_OFFSET) as *mut u8
202        }
203    }
204
205    #[inline]
206    ///Returns number of bytes left (not written yet)
207    pub const fn remaining(&self) -> usize {
208        Self::capacity() - self.len()
209    }
210
211    #[inline]
212    ///Returns reference to underlying storage as it is.
213    pub const fn as_storage(&self) -> &[mem::MaybeUninit<u8>; N] {
214        &self.inner
215    }
216
217    #[inline]
218    ///Returns reference to underlying storage as it is.
219    ///
220    ///To safely modify the storage, user must guarantee to write valid UTF-8
221    pub unsafe fn as_mut_storage(&mut self) -> &mut [mem::MaybeUninit<u8>; N] {
222        &mut self.inner
223    }
224
225    #[inline]
226    ///Returns slice to already written data.
227    pub const fn as_slice(&self) -> &[u8] {
228        unsafe {
229            slice::from_raw_parts(self.as_ptr(), self.len())
230        }
231    }
232
233    #[inline]
234    ///Returns mutable slice to already written data.
235    ///
236    ///To safely modify the slice, user must guarantee to write valid UTF-8
237    pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
238        slice::from_raw_parts_mut(self.as_mut_ptr(), self.len())
239    }
240
241    #[inline]
242    ///Returns mutable slice with unwritten parts of the buffer.
243    pub fn as_write_slice(&mut self) -> &mut [mem::MaybeUninit<u8>] {
244        let len = self.len();
245        &mut self.inner[Self::LEN_OFFSET + len..]
246    }
247
248    #[inline(always)]
249    ///Clears the content of buffer.
250    pub fn clear(&mut self) {
251        unsafe {
252            self.set_len(0);
253        }
254    }
255
256    #[inline(always)]
257    ///Returns empty self.
258    pub const fn empty(self) -> Self {
259        unsafe {
260            self.const_set_len(0)
261        }
262    }
263
264    #[inline]
265    ///Shortens the buffer, keeping the first `cursor` elements.
266    ///
267    ///Does nothing if new `cursor` is after current position.
268    ///
269    ///Unsafe as it is up to user to consider character boundary
270    pub unsafe fn truncate(&mut self, len: usize) {
271        if len < self.len() {
272            self.set_len(len);
273        }
274    }
275
276    #[inline]
277    ///Returns buffer overall capacity.
278    pub const fn capacity() -> usize {
279        Self::CAPACITY
280    }
281
282    #[inline]
283    ///Returns number of bytes written.
284    pub const fn len(&self) -> usize {
285        if N == 0 {
286            0
287        } else if N <= CAPACITY_U8 {
288            unsafe {
289                self.inner[0].assume_init() as _
290            }
291        } else if N <= CAPACITY_U16 {
292            unsafe {
293                u16::from_ne_bytes(*(self.inner.as_ptr() as *const [u8; mem::size_of::<u16>()])) as usize
294            }
295        } else {
296            unsafe {
297                usize::from_ne_bytes(*(self.inner.as_ptr() as *const [u8; mem::size_of::<usize>()]))
298            }
299        }
300    }
301
302    #[inline(always)]
303    ///Sets new length of the string.
304    pub const unsafe fn const_set_len(mut self, len: usize) -> Self {
305        if N == 0 {
306            //no length
307        } else if N <= CAPACITY_U8 {
308            self.inner[0] = mem::MaybeUninit::new(len as _);
309        } else if N <= CAPACITY_U16 {
310            let len = (len as u16).to_ne_bytes();
311            self.inner[0] = mem::MaybeUninit::new(len[0]);
312            self.inner[1] = mem::MaybeUninit::new(len[1]);
313        } else {
314            let len = len.to_ne_bytes();
315            let mut idx = 0;
316            loop {
317                self.inner[idx] = mem::MaybeUninit::new(len[idx]);
318                idx = idx.saturating_add(1);
319                if idx >= len.len() {
320                    break;
321                }
322            }
323        }
324
325        self
326    }
327
328    #[inline(always)]
329    ///Sets new length of the string.
330    pub unsafe fn set_len(&mut self, len: usize) {
331        if N == 0 {
332            //No length
333        } else if N <= CAPACITY_U8 {
334            self.inner[0] = mem::MaybeUninit::new(len as _);
335        } else if N <= CAPACITY_U16 {
336            let len = (len as u16).to_ne_bytes();
337            self.inner[0] = mem::MaybeUninit::new(len[0]);
338            self.inner[1] = mem::MaybeUninit::new(len[1]);
339        } else {
340            let len = len.to_ne_bytes();
341            let ptr = self.inner.as_mut_ptr();
342            ptr::copy_nonoverlapping(len.as_ptr(), ptr as *mut _, mem::size_of::<usize>());
343        }
344    }
345
346    #[inline]
347    ///Appends given string without any size checks
348    pub unsafe fn push_str_unchecked(&mut self, text: &str) {
349        ptr::copy_nonoverlapping(text.as_ptr(), self.as_mut_ptr().add(self.len()), text.len());
350        self.set_len(self.len().saturating_add(text.len()));
351    }
352
353    #[inline]
354    ///Appends given string, truncating on overflow, returning number of written bytes
355    pub fn push_str(&mut self, text: &str) -> usize {
356        let mut size = cmp::min(text.len(), self.remaining());
357
358        #[cold]
359        fn shift_by_char_boundary(text: &str, mut size: usize) -> usize {
360            while !text.is_char_boundary(size) {
361                size -= 1;
362            }
363            size
364        }
365
366        if !text.is_char_boundary(size) {
367            //0 is always char boundary so 0 - 1 is impossible
368            size = shift_by_char_boundary(text, size - 1);
369        }
370
371        unsafe {
372            self.push_str_unchecked(&text[..size]);
373        }
374        size
375    }
376
377    #[inline]
378    ///Appends given string, assuming it fits.
379    ///
380    ///On overflow panics with index out of bounds.
381    pub const fn and(self, text: &str) -> Self {
382        unsafe {
383            self.and_unsafe(text.as_bytes())
384        }
385    }
386
387   #[inline]
388    ///Unsafely appends given bytes, assuming valid utf-8.
389    ///
390    ///On overflow panics with index out of bounds as `and`.
391    pub const unsafe fn and_unsafe(mut self, bytes: &[u8]) -> Self {
392        debug_assert!(self.remaining() >= bytes.len(), "Buffer overflow");
393
394        let mut idx = 0;
395        let cursor = self.len();
396        while idx < bytes.len() {
397            self.inner[Self::LEN_OFFSET + cursor + idx] = mem::MaybeUninit::new(bytes[idx]);
398            idx += 1;
399        }
400        self.const_set_len(cursor + bytes.len())
401    }
402
403    #[inline(always)]
404    ///Access str from underlying storage
405    ///
406    ///Returns empty if nothing has been written into buffer yet.
407    pub const fn as_str(&self) -> &str {
408        unsafe {
409            core::str::from_utf8_unchecked(self.as_slice())
410        }
411    }
412
413    #[inline(always)]
414    ///Modifies this string to convert all its characters into ASCII lower case equivalent
415    pub const fn into_ascii_lowercase(mut self) -> Self {
416        let len = Self::LEN_OFFSET + self.len();
417        let mut idx = Self::LEN_OFFSET;
418        loop {
419            if idx >= len {
420                break;
421            }
422
423            self.inner[idx] = unsafe {
424                mem::MaybeUninit::new(self.inner[idx].assume_init().to_ascii_lowercase())
425            };
426            idx = idx.saturating_add(1);
427        }
428        self
429    }
430
431    #[inline(always)]
432    ///Converts this string to its ASCII lower case equivalent in-place.
433    pub fn make_ascii_lowercase(&mut self) {
434        unsafe {
435            self.as_mut_slice().make_ascii_lowercase()
436        }
437    }
438
439    #[inline(always)]
440    ///Modifies this string to convert all its characters into ASCII upper case equivalent
441    pub const fn into_ascii_uppercase(mut self) -> Self {
442        let len = Self::LEN_OFFSET + self.len();
443        let mut idx = Self::LEN_OFFSET;
444        loop {
445            if idx >= len {
446                break;
447            }
448
449            self.inner[idx] = unsafe {
450                mem::MaybeUninit::new(self.inner[idx].assume_init().to_ascii_uppercase())
451            };
452            idx = idx.saturating_add(1);
453        }
454        self
455    }
456
457    #[inline(always)]
458    ///Converts this string to its ASCII upper case equivalent in-place.
459    pub fn make_ascii_uppercase(&mut self) {
460        unsafe {
461            self.as_mut_slice().make_ascii_uppercase()
462        }
463    }
464
465    ///Trims of whitespaces around both ends of the string in place
466    pub fn make_trim(&mut self) {
467        let this = self.as_str();
468        let len = this.len();
469        let mut trim_left_count = 0usize;
470        let mut trim_right_count = 0usize;
471        let mut chars = this.chars();
472        while let Some(ch) = chars.next() {
473            if ch.is_whitespace() {
474                trim_left_count = trim_left_count.saturating_add(ch.len_utf8());
475            } else {
476                break;
477            }
478        }
479
480        for ch in chars.rev() {
481            if ch.is_whitespace() {
482                trim_right_count = trim_right_count.saturating_add(ch.len_utf8());
483            } else {
484                break;
485            }
486        }
487
488        let new_len = len.saturating_sub(trim_left_count).saturating_sub(trim_right_count);
489        if new_len != len {
490            unsafe {
491                //To make sure Miri doesn't complain, you have to derive both pointers from the same one otherwise Miri will 're-borrow' for no reason
492                let dest = self.as_mut_ptr();
493                let src = dest.add(trim_left_count) as *const _;
494                ptr::copy(src, dest, new_len);
495                self.set_len(new_len);
496            }
497        }
498    }
499
500    #[inline]
501    ///Trims of whitespaces on the left in place.
502    pub fn make_trim_left(&mut self) {
503        let this = self.as_str();
504        let len = this.len();
505        let mut trim_count = 0usize;
506        for ch in this.chars() {
507            if ch.is_whitespace() {
508                trim_count = trim_count.saturating_add(ch.len_utf8());
509            } else {
510                break;
511            }
512        }
513
514        let new_len = len.saturating_sub(trim_count);
515        unsafe {
516            let dest = self.as_mut_ptr();
517            let src = dest.add(trim_count);
518            ptr::copy(src, dest, new_len);
519            self.set_len(new_len);
520        }
521    }
522
523    #[inline]
524    ///Trims of whitespaces on the right in place.
525    pub fn make_trim_right(&mut self) {
526        let this = self.as_str();
527        let len = this.len();
528        let mut trim_count = 0usize;
529        for ch in this.chars().rev() {
530            if ch.is_whitespace() {
531                trim_count = trim_count.saturating_add(ch.len_utf8());
532            } else {
533                break;
534            }
535        }
536
537        unsafe {
538            self.set_len(len.saturating_sub(trim_count))
539        }
540    }
541
542    #[inline]
543    ///Removes last character from the buffer, if any present
544    pub fn pop(&mut self) -> Option<char> {
545        let ch = self.chars().rev().next()?;
546        let new_len = self.len() - ch.len_utf8();
547        unsafe {
548            self.set_len(new_len)
549        }
550        Some(ch)
551    }
552}
553
554impl<const S: usize> AsRef<str> for StrBuf<S> {
555    #[inline(always)]
556    fn as_ref(&self) -> &str {
557        self.as_str()
558    }
559}
560
561impl<const S: usize> fmt::Write for StrBuf<S> {
562    #[inline(always)]
563    fn write_str(&mut self, s: &str) -> fmt::Result {
564        if self.push_str(s) == s.len() {
565            Ok(())
566        } else {
567            Err(fmt::Error)
568        }
569    }
570}
571
572impl<const S: usize> fmt::Display for StrBuf<S> {
573    #[inline(always)]
574    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
575        fmt::Display::fmt(self.as_str(), fmt)
576    }
577}
578
579impl<const S: usize> fmt::Debug for StrBuf<S> {
580    #[inline(always)]
581    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
582        fmt::Debug::fmt(self.as_str(), fmt)
583    }
584}
585
586impl<const S: usize> AsRef<[u8]> for StrBuf<S> {
587    #[inline(always)]
588    fn as_ref(&self) -> &[u8] {
589        self.as_slice()
590    }
591}
592
593impl<const S: usize> borrow::Borrow<str> for StrBuf<S> {
594    #[inline(always)]
595    fn borrow(&self) -> &str {
596        self.as_str()
597    }
598}
599
600impl<const S: usize> ops::Deref for StrBuf<S> {
601    type Target = str;
602
603    #[inline(always)]
604    fn deref(&self) -> &str {
605        self.as_str()
606    }
607}
608
609impl<const S: usize> Eq for StrBuf<S> {}
610
611impl<const S: usize> PartialEq<StrBuf<S>> for StrBuf<S> {
612    #[inline(always)]
613    fn eq(&self, other: &Self) -> bool {
614        self.as_str() == other.as_str()
615    }
616}
617
618impl<const S: usize> PartialEq<StrBuf<S>> for &str {
619    #[inline(always)]
620    fn eq(&self, other: &StrBuf<S>) -> bool {
621        *self == other.as_str()
622    }
623}
624
625impl<const S: usize> PartialEq<StrBuf<S>> for str {
626    #[inline(always)]
627    fn eq(&self, other: &StrBuf<S>) -> bool {
628        self == other.as_str()
629    }
630}
631
632impl<const S: usize> PartialEq<str> for StrBuf<S> {
633    #[inline(always)]
634    fn eq(&self, other: &str) -> bool {
635        self.as_str() == other
636    }
637}
638
639impl<const S: usize> PartialEq<&str> for StrBuf<S> {
640    #[inline(always)]
641    fn eq(&self, other: &&str) -> bool {
642        self.as_str() == *other
643    }
644}
645
646impl<const S: usize> cmp::Ord for StrBuf<S> {
647    fn cmp(&self, other: &Self) -> cmp::Ordering {
648        self.as_str().cmp(other.as_str())
649    }
650}
651
652impl<const S: usize> PartialOrd for StrBuf<S> {
653    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
654        Some(self.cmp(other))
655    }
656}
657
658impl<const S: usize> hash::Hash for StrBuf<S> {
659    fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
660        self.as_str().hash(hasher)
661    }
662}
663
664impl<const S: usize> core::convert::TryFrom<&str> for StrBuf<S> {
665    type Error = StrBufError;
666
667    #[inline(always)]
668    fn try_from(text: &str) -> Result<Self, Self::Error> {
669        Self::from_str_checked(text)
670    }
671}
672
673impl<const S: usize> core::str::FromStr for StrBuf<S> {
674    type Err = StrBufError;
675
676    #[inline(always)]
677    fn from_str(text: &str) -> Result<Self, Self::Err> {
678        Self::from_str_checked(text)
679    }
680}