kstring/
stack.rs

1use std::fmt;
2
3pub(crate) type Len = u8;
4
5/// Fixed-size stack-allocated string
6#[derive(Copy, Clone)]
7pub struct StackString<const CAPACITY: usize> {
8    len: Len,
9    buffer: StrBuffer<CAPACITY>,
10}
11
12impl<const CAPACITY: usize> StackString<CAPACITY> {
13    pub const CAPACITY: usize = CAPACITY;
14    pub const EMPTY: Self = Self::empty();
15
16    const fn empty() -> Self {
17        Self {
18            len: 0,
19            buffer: StrBuffer::empty(),
20        }
21    }
22
23    /// Create a `StackString` from a `&str`, if it'll fit within `Self::CAPACITY`
24    ///
25    /// # Examples
26    ///
27    /// Basic usage:
28    ///
29    /// ```
30    /// let s = kstring::StackString::<3>::try_new("foo");
31    /// assert_eq!(s.as_deref(), Some("foo"));
32    /// let s = kstring::StackString::<3>::try_new("foobar");
33    /// assert_eq!(s, None);
34    /// ```
35    #[inline]
36    #[must_use]
37    pub fn try_new(s: &str) -> Option<Self> {
38        let len = s.as_bytes().len();
39        if len <= Self::CAPACITY {
40            #[cfg(feature = "unsafe")]
41            let stack = {
42                unsafe {
43                    // SAFETY: We've confirmed `len` is within size
44                    Self::new_unchecked(s)
45                }
46            };
47            #[cfg(not(feature = "unsafe"))]
48            let stack = { Self::new(s) };
49            Some(stack)
50        } else {
51            None
52        }
53    }
54
55    /// Create a `StackString` from a `&str`
56    ///
57    /// # Panic
58    ///
59    /// Calling this function with a string larger than `Self::CAPACITY` will panic
60    ///
61    /// # Examples
62    ///
63    /// Basic usage:
64    ///
65    /// ```
66    /// let s = kstring::StackString::<3>::new("foo");
67    /// assert_eq!(s, "foo");
68    /// ```
69    #[inline]
70    #[must_use]
71    pub fn new(s: &str) -> Self {
72        let len = s.as_bytes().len() as u8;
73        debug_assert!(Self::CAPACITY <= Len::MAX.into());
74        let buffer = StrBuffer::new(s);
75        Self { len, buffer }
76    }
77
78    /// Create a `StackString` from a `&str`
79    ///
80    /// # Safety
81    ///
82    /// Calling this function with a string larger than `Self::CAPACITY` is undefined behavior.
83    ///
84    /// # Examples
85    ///
86    /// Basic usage:
87    ///
88    /// ```
89    /// let s = unsafe {
90    ///     // SAFETY: Literal is short-enough
91    ///     kstring::StackString::<3>::new_unchecked("foo")
92    /// };
93    /// assert_eq!(s, "foo");
94    /// ```
95    #[inline]
96    #[must_use]
97    #[cfg(feature = "unsafe")]
98    pub unsafe fn new_unchecked(s: &str) -> Self {
99        let len = s.as_bytes().len() as u8;
100        debug_assert!(Self::CAPACITY <= Len::MAX.into());
101        let buffer = StrBuffer::new_unchecked(s);
102        Self { len, buffer }
103    }
104
105    /// Extracts a string slice containing the entire `StackString`.
106    ///
107    /// # Examples
108    ///
109    /// Basic usage:
110    ///
111    /// ```
112    /// let s = kstring::StackString::<3>::try_new("foo").unwrap();
113    ///
114    /// assert_eq!("foo", s.as_str());
115    /// ```
116    #[inline]
117    #[must_use]
118    pub fn as_str(&self) -> &str {
119        let len = self.len as usize;
120        #[cfg(feature = "unsafe")]
121        unsafe {
122            // SAFETY: Constructors guarantee that `buffer[..len]` is a `str`,
123            // and we don't mutate the data afterwards.
124            self.buffer.as_str_unchecked(len)
125        }
126        #[cfg(not(feature = "unsafe"))]
127        self.buffer.as_str(len)
128    }
129
130    /// Converts a `StackString` into a mutable string slice.
131    ///
132    /// # Examples
133    ///
134    /// Basic usage:
135    ///
136    /// ```
137    /// let mut s = kstring::StackString::<6>::try_new("foobar").unwrap();
138    /// let s_mut_str = s.as_mut_str();
139    ///
140    /// s_mut_str.make_ascii_uppercase();
141    ///
142    /// assert_eq!("FOOBAR", s_mut_str);
143    /// ```
144    #[inline]
145    #[must_use]
146    pub fn as_mut_str(&mut self) -> &mut str {
147        let len = self.len as usize;
148        #[cfg(feature = "unsafe")]
149        unsafe {
150            // SAFETY: Constructors guarantee that `buffer[..len]` is a `str`,
151            // and we don't mutate the data afterwards.
152            self.buffer.as_mut_str_unchecked(len)
153        }
154        #[cfg(not(feature = "unsafe"))]
155        self.buffer.as_mut_str(len)
156    }
157
158    /// Returns the length of this `StasckString`, in bytes, not [`char`]s or
159    /// graphemes. In other words, it might not be what a human considers the
160    /// length of the string.
161    ///
162    /// # Examples
163    ///
164    /// Basic usage:
165    ///
166    /// ```
167    /// let a = kstring::StackString::<3>::try_new("foo").unwrap();
168    /// assert_eq!(a.len(), 3);
169    ///
170    /// let fancy_f = kstring::StackString::<4>::try_new("ƒoo").unwrap();
171    /// assert_eq!(fancy_f.len(), 4);
172    /// assert_eq!(fancy_f.chars().count(), 3);
173    /// ```
174    #[inline]
175    #[must_use]
176    pub fn len(&self) -> usize {
177        self.len as usize
178    }
179
180    /// Returns `true` if this `StackString` has a length of zero, and `false` otherwise.
181    ///
182    /// # Examples
183    ///
184    /// Basic usage:
185    ///
186    /// ```
187    /// let mut v = kstring::StackString::<20>::EMPTY;
188    /// assert!(v.is_empty());
189    ///
190    /// let a = kstring::StackString::<3>::try_new("foo").unwrap();
191    /// assert!(!a.is_empty());
192    /// ```
193    #[inline]
194    #[must_use]
195    pub fn is_empty(&self) -> bool {
196        self.len() == 0
197    }
198
199    /// Truncates this `StackString`, removing all contents.
200    ///
201    /// # Examples
202    ///
203    /// Basic usage:
204    ///
205    /// ```
206    /// let mut s = kstring::StackString::<3>::try_new("foo").unwrap();
207    ///
208    /// s.clear();
209    ///
210    /// assert!(s.is_empty());
211    /// assert_eq!(0, s.len());
212    /// ```
213    #[inline]
214    pub fn clear(&mut self) {
215        self.len = 0;
216    }
217
218    /// Shortens this `StackString` to the specified length.
219    ///
220    /// If `new_len` is greater than the string's current length, this has no
221    /// effect.
222    ///
223    /// Note that this method has no effect on the allocated capacity
224    /// of the string
225    ///
226    /// # Panics
227    ///
228    /// Panics if `new_len` does not lie on a [`char`] boundary.
229    ///
230    /// # Examples
231    ///
232    /// Basic usage:
233    ///
234    /// ```
235    /// let mut s = kstring::StackString::<5>::try_new("hello").unwrap();
236    ///
237    /// s.truncate(2);
238    ///
239    /// assert_eq!(s, "he");
240    /// ```
241    #[inline]
242    pub fn truncate(&mut self, new_len: usize) {
243        if new_len <= self.len() {
244            assert!(self.is_char_boundary(new_len));
245            self.len = new_len as u8;
246        }
247    }
248}
249
250impl<const CAPACITY: usize> Default for StackString<CAPACITY> {
251    fn default() -> Self {
252        Self::empty()
253    }
254}
255
256impl<const CAPACITY: usize> std::ops::Deref for StackString<CAPACITY> {
257    type Target = str;
258
259    #[inline]
260    fn deref(&self) -> &str {
261        self.as_str()
262    }
263}
264
265impl<const CAPACITY: usize> Eq for StackString<CAPACITY> {}
266
267impl<const C1: usize, const C2: usize> PartialEq<StackString<C1>> for StackString<C2> {
268    #[inline]
269    fn eq(&self, other: &StackString<C1>) -> bool {
270        PartialEq::eq(self.as_str(), other.as_str())
271    }
272}
273
274impl<const CAPACITY: usize> PartialEq<str> for StackString<CAPACITY> {
275    #[inline]
276    fn eq(&self, other: &str) -> bool {
277        PartialEq::eq(self.as_str(), other)
278    }
279}
280
281impl<'s, const CAPACITY: usize> PartialEq<&'s str> for StackString<CAPACITY> {
282    #[inline]
283    fn eq(&self, other: &&str) -> bool {
284        PartialEq::eq(self.as_str(), *other)
285    }
286}
287
288impl<const CAPACITY: usize> PartialEq<String> for StackString<CAPACITY> {
289    #[inline]
290    fn eq(&self, other: &String) -> bool {
291        PartialEq::eq(self.as_str(), other.as_str())
292    }
293}
294
295impl<const CAPACITY: usize> Ord for StackString<CAPACITY> {
296    #[inline]
297    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
298        self.as_str().cmp(other.as_str())
299    }
300}
301
302impl<const C1: usize, const C2: usize> PartialOrd<StackString<C1>> for StackString<C2> {
303    #[inline]
304    fn partial_cmp(&self, other: &StackString<C1>) -> Option<std::cmp::Ordering> {
305        self.as_str().partial_cmp(other.as_str())
306    }
307}
308
309impl<const CAPACITY: usize> PartialOrd<str> for StackString<CAPACITY> {
310    #[inline]
311    fn partial_cmp(&self, other: &str) -> Option<std::cmp::Ordering> {
312        self.as_str().partial_cmp(other)
313    }
314}
315
316impl<'s, const CAPACITY: usize> PartialOrd<&'s str> for StackString<CAPACITY> {
317    #[inline]
318    fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
319        self.as_str().partial_cmp(other)
320    }
321}
322
323impl<const CAPACITY: usize> PartialOrd<String> for StackString<CAPACITY> {
324    #[inline]
325    fn partial_cmp(&self, other: &String) -> Option<std::cmp::Ordering> {
326        self.as_str().partial_cmp(other.as_str())
327    }
328}
329
330impl<const CAPACITY: usize> std::hash::Hash for StackString<CAPACITY> {
331    #[inline]
332    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
333        self.as_str().hash(state);
334    }
335}
336
337impl<const CAPACITY: usize> fmt::Debug for StackString<CAPACITY> {
338    #[inline]
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        fmt::Debug::fmt(self.as_str(), f)
341    }
342}
343
344impl<const CAPACITY: usize> fmt::Display for StackString<CAPACITY> {
345    #[inline]
346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347        fmt::Display::fmt(self.as_str(), f)
348    }
349}
350
351impl<const CAPACITY: usize> AsRef<str> for StackString<CAPACITY> {
352    #[inline]
353    fn as_ref(&self) -> &str {
354        self.as_str()
355    }
356}
357
358impl<const CAPACITY: usize> AsRef<[u8]> for StackString<CAPACITY> {
359    #[inline]
360    fn as_ref(&self) -> &[u8] {
361        self.as_bytes()
362    }
363}
364
365impl<const CAPACITY: usize> AsRef<std::ffi::OsStr> for StackString<CAPACITY> {
366    #[inline]
367    fn as_ref(&self) -> &std::ffi::OsStr {
368        (**self).as_ref()
369    }
370}
371
372impl<const CAPACITY: usize> AsRef<std::path::Path> for StackString<CAPACITY> {
373    #[inline]
374    fn as_ref(&self) -> &std::path::Path {
375        std::path::Path::new(self)
376    }
377}
378
379impl<const CAPACITY: usize> std::borrow::Borrow<str> for StackString<CAPACITY> {
380    #[inline]
381    fn borrow(&self) -> &str {
382        self.as_str()
383    }
384}
385
386#[derive(Copy, Clone)]
387#[repr(transparent)]
388pub(crate) struct StrBuffer<const CAPACITY: usize>([u8; CAPACITY]);
389
390impl<const CAPACITY: usize> StrBuffer<CAPACITY> {
391    pub(crate) const fn empty() -> Self {
392        let array = [0; CAPACITY];
393        StrBuffer(array)
394    }
395
396    #[inline]
397    pub(crate) fn new(s: &str) -> Self {
398        let len = s.as_bytes().len();
399        debug_assert!(len <= CAPACITY);
400        let mut buffer = Self::default();
401        if let Some(buffer) = buffer.0.get_mut(..len) {
402            buffer.copy_from_slice(s.as_bytes());
403        } else {
404            panic!("`{}` is larger than capacity {}", s, CAPACITY);
405        }
406        buffer
407    }
408
409    #[inline]
410    #[cfg(not(feature = "unsafe"))]
411    pub(crate) fn as_str(&self, len: usize) -> &str {
412        let slice = self.0.get(..len).unwrap();
413        std::str::from_utf8(slice).unwrap()
414    }
415
416    #[inline]
417    #[cfg(not(feature = "unsafe"))]
418    pub(crate) fn as_mut_str(&mut self, len: usize) -> &mut str {
419        let slice = self.0.get_mut(..len).unwrap();
420        std::str::from_utf8_mut(slice).unwrap()
421    }
422}
423
424impl<const CAPACITY: usize> StrBuffer<CAPACITY> {
425    #[inline]
426    #[cfg(feature = "unsafe")]
427    pub(crate) unsafe fn new_unchecked(s: &str) -> Self {
428        let len = s.as_bytes().len();
429        debug_assert!(len <= CAPACITY);
430        let mut buffer = Self::default();
431        buffer
432            .0
433            .get_unchecked_mut(..len)
434            .copy_from_slice(s.as_bytes());
435        buffer
436    }
437
438    #[inline]
439    #[cfg(feature = "unsafe")]
440    pub(crate) unsafe fn as_str_unchecked(&self, len: usize) -> &str {
441        let slice = self.0.get_unchecked(..len);
442        std::str::from_utf8_unchecked(slice)
443    }
444
445    #[inline]
446    #[cfg(feature = "unsafe")]
447    pub(crate) unsafe fn as_mut_str_unchecked(&mut self, len: usize) -> &mut str {
448        let slice = self.0.get_unchecked_mut(..len);
449        std::str::from_utf8_unchecked_mut(slice)
450    }
451}
452
453impl<const CAPACITY: usize> Default for StrBuffer<CAPACITY> {
454    fn default() -> Self {
455        Self::empty()
456    }
457}