sphinx/runtime/strings/
buffer.rs

1/*
2    This is a port of the inline string data structure from flexstr <https://github.com/nu11ptr/flexstr>
3    The original implementation was written by Scott Meeuwsen <https://github.com/nu11ptr> and can be found here:
4    <https://github.com/nu11ptr/flexstr/blob/be94d3647bad6a626aa40c1e20290a71c1ee8a74/src/inline.rs>
5*/
6
7use core::ptr;
8use core::str;
9use core::fmt;
10use core::mem::{self, MaybeUninit};
11use core::borrow::Borrow;
12
13
14// TODO evaluate what size makes the most sense for the StringValue struct
15
16/// Using this inline capacity will result in a type with the same memory size as a builtin `String`
17// pub const STRING_SIZED_INLINE: usize = mem::size_of::<String>() - 2;
18
19#[derive(Clone, Copy)]
20pub struct StrBuffer<const N: usize> {
21    data: [MaybeUninit<u8>; N],
22    len: u8,
23}
24
25// Conversion from other string types
26impl<'s, const N: usize> TryFrom<&'s String> for StrBuffer<N> {
27    type Error = &'s String;
28    #[inline]
29    fn try_from(value: &'s String) -> Result<Self, Self::Error> {
30        Self::try_from(value)
31    }
32}
33
34impl<'s, const N: usize> TryFrom<&'s str> for StrBuffer<N> {
35    type Error = &'s str;
36    #[inline]
37    fn try_from(value: &'s str) -> Result<Self, Self::Error> {
38        Self::try_from(value)
39    }
40}
41
42impl<const N: usize> StrBuffer<N> {
43    /// Create a new empty `StrBuffer`
44    #[inline]
45    pub fn new() -> Self {
46        Self {
47            len: 0,
48            data: [MaybeUninit::<u8>::uninit(); N]
49        }
50    }
51    
52    /// Attempts to return a new `StrBuffer` if the source string is short enough to be copied.
53    /// If not, the source is returned as the error.
54    #[inline]
55    fn try_from<S: AsRef<str>>(s: S) -> Result<Self, S> {
56        let s_ref = s.as_ref();
57
58        if s_ref.len() <= Self::capacity() {
59            unsafe { Ok(Self::new_unchecked(s_ref)) }
60        } else {
61            Err(s)
62        }
63    }
64    
65    #[inline]
66    unsafe fn new_unchecked(s: &str) -> Self {
67        // SAFETY: This is safe because while uninitialized to start, we copy the the str contents
68        // over the top. We check to ensure it is not too long in `try_new` and don't call this
69        // function directly. The copy is restrained to the length of the str.
70
71        // Declare array, but keep uninitialized (we will overwrite momentarily)
72        let mut data = [MaybeUninit::<u8>::uninit(); N];
73        // Copy contents of &str to our data buffer
74        ptr::copy_nonoverlapping(s.as_ptr(), data.as_mut_ptr().cast::<u8>(), s.len());
75
76        Self {
77            len: s.len() as u8,
78            data,
79        }
80    }
81    
82    #[inline]
83    fn from_array(data: [MaybeUninit<u8>; N], len: u8) -> Self {
84        Self { data, len }
85    }
86
87    /// Returns the capacity of this inline string
88    #[inline]
89    pub fn capacity() -> usize { N }
90
91    /// Returns the length of this `InlineFlexStr` in bytes
92    #[inline]
93    pub fn len(&self) -> usize { self.len as usize }
94
95    /// Return true if the inline string is empty else false
96    #[inline]
97    pub fn is_empty(&self) -> bool { self.len == 0 }
98
99    pub fn try_push(&mut self, ch: char) -> fmt::Result {
100        let mut buf = [0u8; 4];
101        self.try_push_str(ch.encode_utf8(&mut buf))
102    }
103
104    /// Attempts to concatenate the `&str` if there is room. It returns true if it is able to do so.
105    #[inline]
106    pub fn try_push_str<S: AsRef<str>>(&mut self, s: S) -> fmt::Result {
107        let s_ref = s.as_ref();
108        
109        if self.len() + s_ref.len() <= Self::capacity() {
110            // Point to the location directly after our string
111            let data = self.data[self.len as usize..].as_mut_ptr().cast::<u8>();
112
113            unsafe {
114                // SAFETY: We know the buffer is large enough and that the location is not overlapping
115                // this one (we know that because we have ownership of one of them)
116                // Copy contents of &str to our data buffer
117                ptr::copy_nonoverlapping(s_ref.as_ptr(), data, s_ref.len());
118            }
119            self.len += s_ref.len() as u8;
120            Ok(())
121        } else {
122            Err(fmt::Error::default())
123        }
124    }
125}
126
127
128impl<const N: usize> core::ops::Deref for StrBuffer<N> {
129    type Target = str;
130
131    #[inline]
132    fn deref(&self) -> &Self::Target {
133        let data = &self.data[..self.len()];
134
135        unsafe {
136            // SAFETY: The contents are always obtained from a valid UTF8 str, so they must be valid
137            // Additionally, we clamp the size of the slice passed to be no longer than our str length
138            let data = &*(data as *const [mem::MaybeUninit<u8>] as *const [u8]);
139            str::from_utf8_unchecked(data)
140        }
141    }
142}
143
144impl<const N: usize> AsRef<str> for StrBuffer<N> {
145    fn as_ref(&self) -> &str { &*self }
146}
147
148impl<const N: usize> Borrow<str> for StrBuffer<N> {
149    fn borrow(&self) -> &str { &*self }
150}
151
152impl<const N: usize> fmt::Write for StrBuffer<N> {
153    fn write_str(&mut self, s: &str) -> fmt::Result {
154        self.try_push_str(s)
155    }
156
157    fn write_char(&mut self, c: char) -> fmt::Result {
158        self.try_push(c)
159    }
160}
161
162impl<const N: usize> fmt::Debug for StrBuffer<N> {
163    #[inline]
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        <str as fmt::Debug>::fmt(self, f)
166    }
167}
168
169impl<const N: usize> fmt::Display for StrBuffer<N> {
170    #[inline]
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        <str as fmt::Display>::fmt(self, f)
173    }
174}
175
176
177
178
179#[cfg(test)]
180mod tests {
181    use crate::runtime::strings::buffer::StrBuffer;
182
183    #[test]
184    fn empty() {
185        let lit = "";
186        let s: StrBuffer<22> = lit.try_into().expect("bad inline str");
187        assert_eq!(&*s, lit);
188        assert_eq!(s.len(), lit.len())
189    }
190
191    #[test]
192    fn good_init() {
193        let lit = "inline";
194        let s: StrBuffer<22> = lit.try_into().expect("bad inline str");
195        assert_eq!(&*s, lit);
196        assert_eq!(s.len(), lit.len())
197    }
198
199    #[test]
200    fn bad_init() {
201        let lit = "This is way too long to be an inline string!!!";
202        let s = <StrBuffer<22>>::try_from(lit).unwrap_err();
203        assert_eq!(s, lit);
204        assert_eq!(s.len(), lit.len())
205    }
206
207    #[test]
208    fn good_concat() {
209        let lit = "Inline";
210        let lit2 = " me";
211        let mut s = <StrBuffer<22>>::try_from(lit).expect("bad inline str");
212        assert!(s.try_push_str(lit2).is_ok());
213        assert_eq!(&*s, lit.to_string() + lit2);
214    }
215
216    #[test]
217    fn bad_concat() {
218        let lit = "This is";
219        let lit2 = " way too long to be an inline string!!!";
220        let mut s = <StrBuffer<22>>::try_from(lit).expect("bad inline str");
221        assert!(s.try_push_str(lit2).is_err());
222        assert_eq!(&*s, lit);
223    }
224}