Skip to main content

pijul_core/
small_string.rs

1pub const MAX_LENGTH: usize = 255;
2
3/// A string of length at most 255, with a more compact on-disk
4/// encoding.
5#[repr(C)]
6pub struct SmallString {
7    pub len: u8,
8    pub str: [u8; MAX_LENGTH],
9}
10
11/// A borrowed version of `SmallStr`.
12#[repr(C)]
13pub struct SmallStr {
14    len: u8,
15    _str: [u8],
16}
17
18impl std::hash::Hash for SmallStr {
19    fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
20        self.as_bytes().hash(hasher)
21    }
22}
23
24impl Clone for SmallString {
25    fn clone(&self) -> Self {
26        SmallString {
27            len: self.len,
28            str: self.str,
29        }
30    }
31}
32
33impl std::fmt::Debug for SmallString {
34    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
35        use std::ops::Deref;
36        self.deref().fmt(fmt)
37    }
38}
39
40impl std::fmt::Display for SmallString {
41    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
42        use std::ops::Deref;
43        self.deref().fmt(fmt)
44    }
45}
46
47impl PartialEq for SmallStr {
48    fn eq(&self, x: &SmallStr) -> bool {
49        self.as_str().eq(x.as_str())
50    }
51}
52
53impl std::ops::Deref for SmallString {
54    type Target = SmallStr;
55    fn deref(&self) -> &Self::Target {
56        let len = self.len as usize;
57        unsafe {
58            std::mem::transmute(std::slice::from_raw_parts(
59                self as *const Self as *const u8,
60                1 + len,
61            ))
62        }
63    }
64}
65
66impl AsRef<SmallStr> for SmallString {
67    fn as_ref(&self) -> &SmallStr {
68        let len = self.len as usize;
69        unsafe {
70            std::mem::transmute(std::slice::from_raw_parts(
71                self as *const Self as *const u8,
72                1 + len,
73            ))
74        }
75    }
76}
77
78impl AsMut<SmallStr> for SmallString {
79    fn as_mut(&mut self) -> &mut SmallStr {
80        let len = self.len as usize;
81        unsafe {
82            std::mem::transmute(std::slice::from_raw_parts_mut(
83                self as *mut Self as *mut u8,
84                1 + len,
85            ))
86        }
87    }
88}
89
90impl std::ops::DerefMut for SmallString {
91    fn deref_mut(&mut self) -> &mut Self::Target {
92        let len = self.len as usize;
93        unsafe {
94            std::mem::transmute(std::slice::from_raw_parts_mut(
95                self as *mut Self as *mut u8,
96                1 + len,
97            ))
98        }
99    }
100}
101
102#[test]
103fn eq() {
104    let s0 = SmallString::from_str("blabla");
105    let s1 = SmallString::from_str("blabla");
106    assert_eq!(s0, s1);
107
108    assert_eq!(s0, s1);
109
110    assert_eq!(s0, s1);
111    assert_eq!(s0, s0);
112    assert_eq!(s1, s1);
113}
114
115#[test]
116fn debug() {
117    let s = SmallString::from_str("blabla");
118    assert_eq!(format!("{:?}", s), "\"blabla\"");
119}
120
121impl Eq for SmallStr {}
122
123impl PartialEq for SmallString {
124    fn eq(&self, x: &SmallString) -> bool {
125        self.as_str().eq(x.as_str())
126    }
127}
128impl Eq for SmallString {}
129
130impl std::hash::Hash for SmallString {
131    fn hash<H: std::hash::Hasher>(&self, x: &mut H) {
132        self.as_str().hash(x)
133    }
134}
135
136impl PartialOrd for SmallStr {
137    fn partial_cmp(&self, x: &SmallStr) -> Option<std::cmp::Ordering> {
138        Some(self.cmp(x))
139    }
140}
141impl Ord for SmallStr {
142    fn cmp(&self, x: &SmallStr) -> std::cmp::Ordering {
143        self.as_str().cmp(x.as_str())
144    }
145}
146
147impl PartialOrd for SmallString {
148    fn partial_cmp(&self, x: &SmallString) -> Option<std::cmp::Ordering> {
149        Some(self.cmp(x))
150    }
151}
152impl Ord for SmallString {
153    fn cmp(&self, x: &SmallString) -> std::cmp::Ordering {
154        self.as_str().cmp(x.as_str())
155    }
156}
157
158#[test]
159fn ord() {
160    let s0 = SmallString::from_str("1234");
161    let s1 = SmallString::from_str("5678");
162    assert!(s0 < s1);
163    assert!(s0 < s1);
164    assert_eq!(s0.cmp(&s1), std::cmp::Ordering::Less);
165}
166
167impl std::fmt::Debug for SmallStr {
168    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
169        self.as_str().fmt(fmt)
170    }
171}
172
173impl std::fmt::Display for SmallStr {
174    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
175        self.as_str().fmt(fmt)
176    }
177}
178
179impl Default for SmallString {
180    fn default() -> Self {
181        Self {
182            len: 0,
183            str: [0; MAX_LENGTH],
184        }
185    }
186}
187
188#[derive(Debug, Error)]
189pub enum Error {
190    #[error("Small string too long: {length}")]
191    TooLong { length: usize },
192}
193
194impl std::str::FromStr for SmallString {
195    type Err = Error;
196
197    fn from_str(s: &str) -> Result<Self, Error> {
198        if s.len() > MAX_LENGTH {
199            return Err(Error::TooLong { length: s.len() });
200        }
201        let mut b = SmallString {
202            len: s.len() as u8,
203            str: [0; MAX_LENGTH],
204        };
205        b.clone_from_str(s);
206        Ok(b)
207    }
208}
209
210impl SmallString {
211    pub fn new() -> Self {
212        Self::default()
213    }
214
215    pub fn from_str(s: &str) -> Self {
216        <SmallString as std::str::FromStr>::from_str(s).expect("string too long for SmallString")
217    }
218    /// ```ignore
219    /// use pijul_core::small_string::*;
220    /// let mut s = SmallString::from_str("blah!");
221    /// assert_eq!(s.len(), s.as_str().len());
222    /// ```
223    pub fn len(&self) -> usize {
224        self.len as usize
225    }
226
227    /// ```ignore
228    /// use pijul_core::small_string::*;
229    /// let mut s = SmallString::from_str("blah");
230    /// s.clear();
231    /// assert_eq!(s.as_str(), "");
232    /// assert!(s.is_empty());
233    /// ```
234    pub fn is_empty(&self) -> bool {
235        self.len() == 0
236    }
237
238    pub fn clone_from_str(&mut self, s: &str) {
239        self.len = s.len() as u8;
240        self.str[..s.len()].copy_from_slice(s.as_bytes());
241    }
242
243    /// ```ignore
244    /// use pijul_core::small_string::*;
245    /// let mut s = SmallString::from_str("blah");
246    /// s.clear();
247    /// assert!(s.is_empty());
248    /// ```
249    pub fn clear(&mut self) {
250        self.len = 0;
251    }
252    pub fn push_str(&mut self, s: &str) {
253        let l = self.len as usize;
254        assert!(l + s.len() <= 0xff);
255        self.str[l..l + s.len()].copy_from_slice(s.as_bytes());
256        self.len += s.len() as u8;
257    }
258
259    pub fn as_str(&self) -> &str {
260        use std::ops::Deref;
261        self.deref().as_str()
262    }
263
264    pub fn as_bytes(&self) -> &[u8] {
265        use std::ops::Deref;
266        self.deref().as_bytes()
267    }
268}
269
270impl SmallStr {
271    /// ```ignore
272    /// use pijul_core::small_string::*;
273    /// let mut s = SmallString::from_str("");
274    /// assert!(s.as_small_str().is_empty());
275    /// s.push_str("blah");
276    /// assert!(!s.as_small_str().is_empty());
277    /// ```
278    pub fn is_empty(&self) -> bool {
279        self.len() == 0
280    }
281
282    /// ```ignore
283    /// use pijul_core::small_string::*;
284    /// let mut s = SmallString::from_str("blah");
285    /// assert_eq!(s.as_small_str().len(), "blah".len())
286    /// ```
287    pub fn len(&self) -> usize {
288        self.len as usize
289    }
290
291    pub fn as_str(&self) -> &str {
292        unsafe { std::str::from_utf8_unchecked(self.as_bytes()) }
293    }
294
295    pub fn as_bytes(&self) -> &[u8] {
296        let s: &[u8] = unsafe { std::mem::transmute(self) };
297        &s[1..]
298    }
299
300    pub fn to_owned(&self) -> SmallString {
301        let mut str = [0; 255];
302        str[..self.len as usize].clone_from_slice(&self._str[..self.len as usize]);
303        SmallString { len: self.len, str }
304    }
305}
306
307/// Faster than running doc tests.
308#[test]
309fn all_doc_tests() {
310    {
311        let s = SmallString::from_str("blah!");
312        assert_eq!(s.len(), s.as_str().len());
313    }
314    {
315        let mut s = SmallString::from_str("blah");
316        s.clear();
317        assert_eq!(s.as_str(), "");
318        assert!(s.is_empty());
319    }
320    {
321        let mut s = SmallString::from_str("blah");
322        s.clear();
323        assert!(s.is_empty());
324    }
325    {
326        let mut s = SmallString::from_str("");
327        assert!(s.is_empty());
328        s.push_str("blah");
329        assert!(!s.is_empty());
330    }
331    {
332        let s = SmallString::from_str("blah");
333        assert_eq!(s.len(), "blah".len())
334    }
335}
336
337impl sanakirja::UnsizedStorable for SmallStr {
338    const ALIGN: usize = 1;
339
340    fn size(&self) -> usize {
341        1 + self.len as usize
342    }
343    unsafe fn write_to_page(&self, p: *mut u8) {
344        unsafe {
345            std::ptr::copy(&self.len, p, 1 + self.len as usize);
346            debug!(
347                "writing {:?}",
348                std::slice::from_raw_parts(p, 1 + self.len as usize)
349            );
350        }
351    }
352    unsafe fn from_raw_ptr<'a, T>(_: &T, p: *const u8) -> &'a Self {
353        unsafe { smallstr_from_raw_ptr(p) }
354    }
355    unsafe fn onpage_size(p: *const u8) -> usize {
356        unsafe {
357            let len = *p as usize;
358            debug!("onpage_size {:?}", std::slice::from_raw_parts(p, 1 + len));
359            1 + len
360        }
361    }
362}
363
364impl sanakirja::Storable for SmallStr {
365    fn compare<T>(&self, _: &T, x: &Self) -> std::cmp::Ordering {
366        self.cmp(x)
367    }
368    type PageReferences = std::iter::Empty<u64>;
369    fn page_references(&self) -> Self::PageReferences {
370        std::iter::empty()
371    }
372}
373
374impl ::sanakirja::debug::Check for SmallStr {}
375
376unsafe fn smallstr_from_raw_ptr<'a>(p: *const u8) -> &'a SmallStr {
377    unsafe {
378        let len = *p as usize;
379        std::mem::transmute(std::slice::from_raw_parts(p, 1 + len))
380    }
381}
382
383#[test]
384fn smallstr_repr() {
385    use sanakirja::UnsizedStorable;
386    let o = SmallString::from_str("blablabla");
387    let mut x = vec![0u8; 200];
388    unsafe {
389        o.write_to_page(x.as_mut_ptr());
390        let p = smallstr_from_raw_ptr(x.as_ptr());
391        assert_eq!(p.as_str(), "blablabla")
392    }
393}