smart_string/pascal_string/
mod.rs

1use std::borrow::Borrow;
2use std::borrow::Cow;
3use std::cmp;
4use std::fmt;
5use std::hash::Hash;
6use std::hash::Hasher;
7use std::ops;
8use std::rc::Rc;
9use std::str::from_utf8_unchecked;
10use std::str::from_utf8_unchecked_mut;
11use std::sync::Arc;
12
13use crate::DisplayExt;
14
15mod error;
16#[cfg(feature = "serde")]
17mod with_serde;
18
19pub use error::TryFromBytesError;
20pub use error::TryFromStrError;
21
22#[derive(Clone, Copy)]
23#[repr(C)]
24pub struct PascalString<const CAPACITY: usize> {
25    len: u8,
26    data: [u8; CAPACITY],
27}
28
29impl<const CAPACITY: usize> PascalString<CAPACITY> {
30    pub const CAPACITY: usize = {
31        assert!(
32            CAPACITY <= u8::MAX as usize,
33            "PascalString max capacity is 255"
34        );
35        CAPACITY
36    };
37
38    #[inline(always)]
39    pub const fn new() -> Self {
40        // This line triggers a compile time error, if CAPACITY > 255.
41        // TODO look for a better way to assert CAPACITY.
42        let _ = Self::CAPACITY;
43
44        Self {
45            len: 0,
46            data: [0; CAPACITY],
47        }
48    }
49
50    /// Creates a new `PascalString<CAPACITY>` from a `&str`.
51    /// If the length of the string exceeds `CAPACITY`,
52    /// the string is truncated at the nearest valid UTF-8 boundary
53    /// to ensure its length does not exceed `CAPACITY`.
54    #[inline]
55    pub fn from_str_truncated(string: &str) -> Self {
56        let _ = Self::CAPACITY;
57
58        if let Ok(ps) = Self::try_from(string) {
59            return ps;
60        }
61
62        let mut ps = Self::new();
63        ps.push_str_truncated(string);
64        ps
65    }
66
67    #[inline(always)]
68    pub fn into_inner(self) -> (u8, [u8; CAPACITY]) {
69        (self.len, self.data)
70    }
71
72    #[inline(always)]
73    pub const fn capacity(&self) -> usize {
74        CAPACITY
75    }
76
77    #[inline(always)]
78    pub fn len(&self) -> usize {
79        self.len as usize
80    }
81
82    #[inline(always)]
83    pub fn is_empty(&self) -> bool {
84        self.len == 0
85    }
86
87    #[inline(always)]
88    pub fn as_str(&self) -> &str {
89        self
90    }
91
92    #[inline(always)]
93    pub fn as_str_mut(&mut self) -> &str {
94        self
95    }
96
97    #[inline]
98    pub fn try_push_str(&mut self, string: &str) -> Result<(), TryFromStrError> {
99        let len = self.len();
100        let new_len = len + string.len();
101
102        if new_len > CAPACITY {
103            return Err(TryFromStrError::TooLong);
104        }
105
106        self.data[len..new_len].copy_from_slice(string.as_bytes());
107        self.len = new_len as u8;
108
109        Ok(())
110    }
111
112    #[inline]
113    pub fn try_push(&mut self, ch: char) -> Result<(), TryFromStrError> {
114        // TODO special case for ch.len_utf8() == 1
115        self.try_push_str(ch.encode_utf8(&mut [0; 4]))
116    }
117
118    /// Returns the remainder of the string that was not pushed.
119    #[inline]
120    pub fn push_str_truncated<'s>(&mut self, string: &'s str) -> &'s str {
121        if self.try_push_str(string).is_ok() {
122            return "";
123        }
124
125        // TODO is there more efficient way to do this?
126        //   Maybe iter four bytes from the end of the slice and find the UTF-8 boundary?
127
128        let mut new_len = self.len();
129        for c in string.chars() {
130            let len = c.len_utf8();
131            if new_len + len > CAPACITY {
132                break;
133            };
134            new_len += len;
135        }
136
137        let pos = new_len - self.len();
138        let (substring, remainder) = string.split_at(pos);
139        self.try_push_str(substring).unwrap();
140
141        remainder
142    }
143
144    #[inline]
145    pub fn truncate(&mut self, new_len: usize) {
146        if new_len <= self.len() {
147            assert!(self.is_char_boundary(new_len));
148            self.len = new_len as u8;
149        }
150    }
151
152    #[inline]
153    pub fn pop(&mut self) -> Option<char> {
154        let ch = self.chars().rev().next()?;
155        let newlen = self.len() - ch.len_utf8();
156        self.len = newlen as u8;
157        Some(ch)
158    }
159
160    #[inline]
161    pub fn clear(&mut self) {
162        self.len = 0;
163    }
164}
165
166// -- Common traits --------------------------------------------------------------------------------
167
168impl<const CAPACITY: usize> Default for PascalString<CAPACITY> {
169    #[inline(always)]
170    fn default() -> Self {
171        let _ = Self::CAPACITY;
172
173        Self::new()
174    }
175}
176
177impl<T: ops::Deref<Target = str> + ?Sized, const CAPACITY: usize> PartialEq<T>
178    for PascalString<CAPACITY>
179{
180    #[inline(always)]
181    fn eq(&self, other: &T) -> bool {
182        self.as_str().eq(other.deref())
183    }
184}
185
186macro_rules! impl_reverse_eq_for_str_types {
187    ($($t:ty),*) => {
188        $(
189            impl<const CAPACITY: usize> PartialEq<PascalString<CAPACITY>> for $t {
190                #[inline(always)]
191                fn eq(&self, other: &PascalString<CAPACITY>) -> bool {
192                    let a: &str = self.as_ref();
193                    let b = other.as_str();
194                    a.eq(b)
195                }
196            }
197
198            impl<const CAPACITY: usize> PartialEq<PascalString<CAPACITY>> for &$t {
199                #[inline(always)]
200                fn eq(&self, other: &PascalString<CAPACITY>) -> bool {
201                    let a: &str = self.as_ref();
202                    let b = other.as_str();
203                    a.eq(b)
204                }
205            }
206
207            impl<const CAPACITY: usize> PartialEq<PascalString<CAPACITY>> for &mut $t {
208                #[inline(always)]
209                fn eq(&self, other: &PascalString<CAPACITY>) -> bool {
210                    let a: &str = self.as_ref();
211                    let b = other.as_str();
212                    a.eq(b)
213                }
214            }
215        )*
216    };
217}
218
219impl_reverse_eq_for_str_types!(String, str, Cow<'_, str>, Box<str>, Rc<str>, Arc<str>);
220
221impl<const CAPACITY: usize> Eq for PascalString<CAPACITY> {}
222
223impl<T: ops::Deref<Target = str>, const CAPACITY: usize> PartialOrd<T> for PascalString<CAPACITY> {
224    #[inline(always)]
225    fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
226        self.as_str().partial_cmp(other.deref())
227    }
228}
229
230impl<const CAPACITY: usize> Ord for PascalString<CAPACITY> {
231    #[inline(always)]
232    fn cmp(&self, other: &Self) -> cmp::Ordering {
233        self.as_str().cmp(other.as_str())
234    }
235}
236
237impl<const CAPACITY: usize> Hash for PascalString<CAPACITY> {
238    #[inline(always)]
239    fn hash<H: Hasher>(&self, state: &mut H) {
240        self.as_str().hash(state)
241    }
242}
243
244// -- Formatting -----------------------------------------------------------------------------------
245
246impl<const CAPACITY: usize> fmt::Debug for PascalString<CAPACITY> {
247    #[inline(always)]
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        let name: PascalString<39> = format_args!("PascalString<{CAPACITY}>")
250            .try_to_fmt()
251            .unwrap_or_else(|_| "PascalString<?>".to_fmt());
252        f.debug_tuple(&name).field(&self.as_str()).finish()
253    }
254}
255
256impl<const CAPACITY: usize> fmt::Display for PascalString<CAPACITY> {
257    #[inline(always)]
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        self.as_str().fmt(f)
260    }
261}
262
263// -- Reference ------------------------------------------------------------------------------------
264
265impl<const CAPACITY: usize> ops::Deref for PascalString<CAPACITY> {
266    type Target = str;
267
268    #[inline(always)]
269    fn deref(&self) -> &Self::Target {
270        // SAFETY: PascalString maintains its length invariant.
271        let bytes = unsafe { self.data.get_unchecked(..self.len()) };
272        // SAFETY: PascalString maintains its utf8 invariant.
273        unsafe { from_utf8_unchecked(bytes) }
274    }
275}
276
277impl<const CAPACITY: usize> ops::DerefMut for PascalString<CAPACITY> {
278    #[inline(always)]
279    fn deref_mut(&mut self) -> &mut Self::Target {
280        let len = self.len();
281        // SAFETY: PascalString maintains its length invariant.
282        let bytes = unsafe { self.data.get_unchecked_mut(..len) };
283        // SAFETY: PascalString maintains its utf8 invariant.
284        unsafe { from_utf8_unchecked_mut(bytes) }
285    }
286}
287
288impl<const CAPACITY: usize> Borrow<str> for PascalString<CAPACITY> {
289    #[inline(always)]
290    fn borrow(&self) -> &str {
291        self
292    }
293}
294
295impl<const CAPACITY: usize> AsRef<str> for PascalString<CAPACITY> {
296    #[inline(always)]
297    fn as_ref(&self) -> &str {
298        self
299    }
300}
301
302impl<const CAPACITY: usize> AsRef<[u8]> for PascalString<CAPACITY> {
303    #[inline(always)]
304    fn as_ref(&self) -> &[u8] {
305        self.as_bytes()
306    }
307}
308
309// -- Conversion -----------------------------------------------------------------------------------
310
311impl<'a, const CAPACITY: usize> TryFrom<&'a [u8]> for PascalString<CAPACITY> {
312    type Error = TryFromBytesError;
313
314    #[inline]
315    fn try_from(bytes: &'a [u8]) -> Result<PascalString<CAPACITY>, Self::Error> {
316        let _ = Self::CAPACITY;
317
318        let string = core::str::from_utf8(bytes)?;
319        Ok(Self::try_from(string)?)
320    }
321}
322
323impl<'a, const CAPACITY: usize> TryFrom<&'a mut str> for PascalString<CAPACITY> {
324    type Error = TryFromStrError;
325
326    #[inline]
327    fn try_from(value: &'a mut str) -> Result<PascalString<CAPACITY>, Self::Error> {
328        Self::try_from(&*value)
329    }
330}
331
332impl<'a, const CAPACITY: usize> TryFrom<&'a str> for PascalString<CAPACITY> {
333    type Error = TryFromStrError;
334
335    #[inline]
336    fn try_from(value: &'a str) -> Result<PascalString<CAPACITY>, Self::Error> {
337        let _ = Self::CAPACITY;
338
339        let bytes = value.as_bytes();
340        let len = bytes.len();
341
342        if len > CAPACITY {
343            return Err(TryFromStrError::TooLong);
344        }
345
346        let data = match <[u8; CAPACITY]>::try_from(bytes).ok() {
347            Some(data) => data,
348            None => {
349                let mut data = [0; CAPACITY];
350                data[..len].copy_from_slice(bytes);
351                data
352            }
353        };
354
355        let len = len as u8;
356
357        Ok(PascalString { len, data })
358    }
359}
360
361impl<const CAPACITY: usize> TryFrom<char> for PascalString<CAPACITY> {
362    type Error = TryFromStrError;
363
364    #[inline]
365    fn try_from(value: char) -> Result<PascalString<CAPACITY>, Self::Error> {
366        let _ = Self::CAPACITY;
367
368        Self::try_from(value.encode_utf8(&mut [0; 4]))
369    }
370}
371
372// -- IO -------------------------------------------------------------------------------------------
373
374impl<const CAPACITY: usize> fmt::Write for PascalString<CAPACITY> {
375    #[inline]
376    fn write_str(&mut self, s: &str) -> fmt::Result {
377        self.try_push_str(s).map_err(|_| fmt::Error)
378    }
379}
380
381// -- Tests ----------------------------------------------------------------------------------------
382
383#[cfg(test)]
384mod tests {
385    use std::mem;
386
387    use super::*;
388
389    #[test]
390    fn test_eq() {
391        use std::fmt::Write;
392
393        let s = String::from("abc");
394        let ps = PascalString::<4>::try_from("abc").unwrap();
395
396        assert_eq!(ps, s);
397        // assert_eq!(ps.as_view(), s);
398        // assert_eq!(ps.as_view(), ps);
399
400        let s = String::from("abcd");
401        let mut ps = PascalString::<4>::new();
402        write!(&mut ps, "abcd").unwrap();
403
404        assert_eq!(ps, s);
405    }
406
407    #[test]
408    fn test_ord() {
409        let ps1 = PascalString::<4>::try_from("abc").unwrap();
410        let ps2 = PascalString::<4>::try_from("abcd").unwrap();
411
412        assert!(ps1 < ps2);
413        assert!(ps1 <= ps2);
414        assert!(ps2 > ps1);
415        assert!(ps2 >= ps1);
416    }
417
418    #[test]
419    fn test_size() {
420        assert_eq!(mem::size_of::<PascalString<0>>(), 1);
421        assert_eq!(mem::size_of::<PascalString<1>>(), 2);
422        assert_eq!(mem::size_of::<PascalString<2>>(), 3);
423        assert_eq!(mem::size_of::<PascalString<3>>(), 4);
424        assert_eq!(mem::size_of::<PascalString<4>>(), 5);
425    }
426
427    // TODO use https://github.com/Manishearth/compiletest-rs to test compile errors.
428    // #[test]
429    fn _test_max_size() {
430        // Every of these lines should not compile with a compile error:
431        // "the evaluated program panicked at 'PascalString max capacity is 255'".
432        //
433        // Also, compiler should point to the line that triggered the error, e.g.:
434        //
435        // note: the above error was encountered while instantiating `fn <PascalString<256> as std::default::Default>::default`
436        //    --> src/lib.rs:254:37
437        //     |
438        // 254 |         let _x: PascalString<256> = PascalString::default();
439        //     |                                     ^^^^^^^^^^^^^^^^^^^^^^^
440        //
441        let _x: PascalString<256> = PascalString::default();
442        let _x: PascalString<256> = PascalString::new();
443        let _x: PascalString<256> = PascalString::try_from("").unwrap();
444        let _x: PascalString<256> = PascalString::from_str_truncated("");
445    }
446
447    #[test]
448    fn test_deref() {
449        let ps = PascalString::<3>::try_from("abc").unwrap();
450        let map: std::collections::HashSet<_> = ["abc"].into_iter().collect();
451        assert!(map.contains(&*ps));
452    }
453}