inlinable_string/
inline_string.rs

1// Copyright 2015, The inlinable_string crate Developers. See the COPYRIGHT file
2// at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT
6// or http://opensource.org/licenses/MIT>, at your option. This file may not be
7// copied, modified, or distributed except according to those terms.
8
9//! A short UTF-8 string that uses inline storage and does no heap
10//! allocation. It may be no longer than `INLINE_STRING_CAPACITY` bytes long.
11//!
12//! The capacity restriction makes many operations that would otherwise be
13//! infallible on `std::string::String` fallible. Additionally, many trait
14//! interfaces don't allow returning an error when a string runs out of space,
15//! and so the trait implementation simply panics. As such, `InlineString` does
16//! not implement `StringExt` and is ***not*** a drop-in replacement for
17//! `std::string::String` in the way that `inlinable_string::InlinableString`
18//! aims to be, and is generally difficult to work with. It is not recommended
19//! to use this type directly unless you really, really want to avoid heap
20//! allocation, can live with the imposed size restrictions, and are willing
21//! work around potential sources of panics (eg, in the `From` trait
22//! implementation).
23//!
24//! # Examples
25//!
26//! ```
27//! use inlinable_string::InlineString;
28//!
29//! let mut s = InlineString::new();
30//! assert!(s.push_str("hi world").is_ok());
31//! assert_eq!(s, "hi world");
32//!
33//! assert!(s.push_str("a really long string that is much bigger than `INLINE_STRING_CAPACITY`").is_err());
34//! assert_eq!(s, "hi world");
35//! ```
36
37use alloc::borrow;
38use core::fmt;
39use core::hash;
40use core::ops;
41use core::ptr;
42use core::str;
43
44
45/// The capacity (in bytes) of inline storage for small strings.
46/// `InlineString::len()` may never be larger than this.
47///
48/// Sometime in the future, when Rust's generics support specializing with
49/// compile-time static integers, this number should become configurable.
50#[cfg(target_pointer_width = "64")]
51pub const INLINE_STRING_CAPACITY: usize = 30;
52#[cfg(target_pointer_width = "32")]
53pub const INLINE_STRING_CAPACITY: usize = 14;
54
55/// A short UTF-8 string that uses inline storage and does no heap allocation.
56///
57/// See the [module level documentation](./index.html) for more.
58#[derive(Clone, Debug, Eq)]
59pub struct InlineString {
60    length: u8,
61    bytes: [u8; INLINE_STRING_CAPACITY],
62}
63
64/// The error returned when there is not enough space in a `InlineString` for the
65/// requested operation.
66#[derive(Debug, PartialEq)]
67pub struct NotEnoughSpaceError;
68
69impl AsRef<str> for InlineString {
70    fn as_ref(&self) -> &str {
71        self.assert_sanity();
72        unsafe { str::from_utf8_unchecked(&self.bytes[..self.len()]) }
73    }
74}
75
76impl AsRef<[u8]> for InlineString {
77    #[inline]
78    fn as_ref(&self) -> &[u8] {
79        self.as_bytes()
80    }
81}
82
83impl AsMut<str> for InlineString {
84    fn as_mut(&mut self) -> &mut str {
85        self.assert_sanity();
86        let length = self.len();
87        unsafe { str::from_utf8_unchecked_mut(&mut self.bytes[..length]) }
88    }
89}
90
91impl AsMut<[u8]> for InlineString {
92    #[inline]
93    fn as_mut(&mut self) -> &mut [u8] {
94        self.assert_sanity();
95        let length = self.len();
96        &mut self.bytes[0..length]
97    }
98}
99
100/// Create a `InlineString` from the given `&str`.
101///
102/// # Panics
103///
104/// If the given string's size is greater than `INLINE_STRING_CAPACITY`, this
105/// method panics.
106impl<'a> From<&'a str> for InlineString {
107    fn from(string: &'a str) -> InlineString {
108        let string_len = string.len();
109        assert!(string_len <= INLINE_STRING_CAPACITY);
110
111        let mut ss = InlineString::new();
112        unsafe {
113            ptr::copy_nonoverlapping(string.as_ptr(), ss.bytes.as_mut_ptr(), string_len);
114        }
115        ss.length = string_len as u8;
116
117        ss.assert_sanity();
118        ss
119    }
120}
121
122impl fmt::Display for InlineString {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
124        self.assert_sanity();
125        write!(f, "{}", self as &str)
126    }
127}
128
129impl fmt::Write for InlineString {
130    fn write_char(&mut self, ch: char) -> Result<(), fmt::Error> {
131        self.push(ch).map_err(|_| fmt::Error)
132    }
133    fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
134        self.push_str(s).map_err(|_| fmt::Error)
135    }
136}
137
138impl hash::Hash for InlineString {
139    #[inline]
140    fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
141        (**self).hash(hasher)
142    }
143}
144
145impl ops::Index<ops::Range<usize>> for InlineString {
146    type Output = str;
147
148    #[inline]
149    fn index(&self, index: ops::Range<usize>) -> &str {
150        self.assert_sanity();
151        &self[..][index]
152    }
153}
154
155impl ops::Index<ops::RangeTo<usize>> for InlineString {
156    type Output = str;
157
158    #[inline]
159    fn index(&self, index: ops::RangeTo<usize>) -> &str {
160        self.assert_sanity();
161        &self[..][index]
162    }
163}
164
165impl ops::Index<ops::RangeFrom<usize>> for InlineString {
166    type Output = str;
167
168    #[inline]
169    fn index(&self, index: ops::RangeFrom<usize>) -> &str {
170        self.assert_sanity();
171        &self[..][index]
172    }
173}
174
175impl ops::Index<ops::RangeFull> for InlineString {
176    type Output = str;
177
178    #[inline]
179    fn index(&self, _index: ops::RangeFull) -> &str {
180        self.assert_sanity();
181        unsafe { str::from_utf8_unchecked(&self.bytes[..self.len()]) }
182    }
183}
184
185impl ops::IndexMut<ops::Range<usize>> for InlineString {
186    #[inline]
187    fn index_mut(&mut self, index: ops::Range<usize>) -> &mut str {
188        self.assert_sanity();
189        &mut self[..][index]
190    }
191}
192
193impl ops::IndexMut<ops::RangeTo<usize>> for InlineString {
194    #[inline]
195    fn index_mut(&mut self, index: ops::RangeTo<usize>) -> &mut str {
196        self.assert_sanity();
197        &mut self[..][index]
198    }
199}
200
201impl ops::IndexMut<ops::RangeFrom<usize>> for InlineString {
202    #[inline]
203    fn index_mut(&mut self, index: ops::RangeFrom<usize>) -> &mut str {
204        self.assert_sanity();
205        &mut self[..][index]
206    }
207}
208
209impl ops::IndexMut<ops::RangeFull> for InlineString {
210    #[inline]
211    fn index_mut(&mut self, _index: ops::RangeFull) -> &mut str {
212        self.assert_sanity();
213        let length = self.len();
214        unsafe { str::from_utf8_unchecked_mut(&mut self.bytes[..length]) }
215    }
216}
217
218impl ops::Deref for InlineString {
219    type Target = str;
220
221    #[inline]
222    fn deref(&self) -> &str {
223        self.assert_sanity();
224        unsafe { str::from_utf8_unchecked(&self.bytes[..self.len()]) }
225    }
226}
227
228impl ops::DerefMut for InlineString {
229    #[inline]
230    fn deref_mut(&mut self) -> &mut str {
231        self.assert_sanity();
232        let length = self.len();
233        unsafe { str::from_utf8_unchecked_mut(&mut self.bytes[..length]) }
234    }
235}
236
237impl Default for InlineString {
238    #[inline]
239    fn default() -> InlineString {
240        InlineString::new()
241    }
242}
243
244impl PartialEq<InlineString> for InlineString {
245    #[inline]
246    fn eq(&self, rhs: &InlineString) -> bool {
247        self.assert_sanity();
248        rhs.assert_sanity();
249        PartialEq::eq(&self[..], &rhs[..])
250    }
251}
252
253macro_rules! impl_eq {
254    ($lhs:ty, $rhs: ty) => {
255        impl<'a> PartialEq<$rhs> for $lhs {
256            #[inline]
257            fn eq(&self, other: &$rhs) -> bool {
258                PartialEq::eq(&self[..], &other[..])
259            }
260        }
261
262        impl<'a> PartialEq<$lhs> for $rhs {
263            #[inline]
264            fn eq(&self, other: &$lhs) -> bool {
265                PartialEq::eq(&self[..], &other[..])
266            }
267        }
268    };
269}
270
271impl_eq! { InlineString, str }
272impl_eq! { InlineString, &'a str }
273impl_eq! { borrow::Cow<'a, str>, InlineString }
274
275impl InlineString {
276    #[cfg_attr(feature = "nightly", allow(inline_always))]
277    #[inline(always)]
278    fn assert_sanity(&self) {
279        debug_assert!(
280            self.length as usize <= INLINE_STRING_CAPACITY,
281            "inlinable_string: internal error: length greater than capacity"
282        );
283        debug_assert!(
284            str::from_utf8(&self.bytes[0..self.length as usize]).is_ok(),
285            "inlinable_string: internal error: contents are not valid UTF-8!"
286        );
287    }
288
289    /// Creates a new string buffer initialized with the empty string.
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// use inlinable_string::InlineString;
295    ///
296    /// let s = InlineString::new();
297    /// ```
298    #[inline]
299    pub fn new() -> InlineString {
300        InlineString {
301            length: 0,
302            bytes: [0; INLINE_STRING_CAPACITY],
303        }
304    }
305
306    /// Returns the underlying byte buffer, encoded as UTF-8. Trailing bytes are
307    /// zeroed.
308    ///
309    /// # Examples
310    ///
311    /// ```
312    /// use inlinable_string::InlineString;
313    ///
314    /// let s = InlineString::from("hello");
315    /// let bytes = s.into_bytes();
316    /// assert_eq!(&bytes[0..5], [104, 101, 108, 108, 111]);
317    /// ```
318    #[inline]
319    pub fn into_bytes(mut self) -> [u8; INLINE_STRING_CAPACITY] {
320        self.assert_sanity();
321        for i in self.len()..INLINE_STRING_CAPACITY {
322            self.bytes[i] = 0;
323        }
324        self.bytes
325    }
326
327    /// Pushes the given string onto this string buffer.
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use inlinable_string::InlineString;
333    ///
334    /// let mut s = InlineString::from("foo");
335    /// s.push_str("bar");
336    /// assert_eq!(s, "foobar");
337    /// ```
338    #[inline]
339    pub fn push_str(&mut self, string: &str) -> Result<(), NotEnoughSpaceError> {
340        self.assert_sanity();
341
342        let string_len = string.len();
343        let new_length = self.len() + string_len;
344
345        if new_length > INLINE_STRING_CAPACITY {
346            return Err(NotEnoughSpaceError);
347        }
348
349        unsafe {
350            ptr::copy_nonoverlapping(
351                string.as_ptr(),
352                self.bytes.as_mut_ptr().offset(self.length as isize),
353                string_len,
354            );
355        }
356        self.length = new_length as u8;
357
358        self.assert_sanity();
359        Ok(())
360    }
361
362    /// Adds the given character to the end of the string.
363    ///
364    /// # Examples
365    ///
366    /// ```
367    /// use inlinable_string::InlineString;
368    ///
369    /// let mut s = InlineString::from("abc");
370    /// s.push('1');
371    /// s.push('2');
372    /// s.push('3');
373    /// assert_eq!(s, "abc123");
374    /// ```
375    #[inline]
376    pub fn push(&mut self, ch: char) -> Result<(), NotEnoughSpaceError> {
377        self.assert_sanity();
378
379        let char_len = ch.len_utf8();
380        let new_length = self.len() + char_len;
381
382        if new_length > INLINE_STRING_CAPACITY {
383            return Err(NotEnoughSpaceError);
384        }
385
386        {
387            let mut slice = &mut self.bytes[self.length as usize..INLINE_STRING_CAPACITY];
388            ch.encode_utf8(&mut slice);
389        }
390        self.length = new_length as u8;
391
392        self.assert_sanity();
393        Ok(())
394    }
395
396    /// Works with the underlying buffer as a byte slice.
397    ///
398    /// # Examples
399    ///
400    /// ```
401    /// use inlinable_string::InlineString;
402    ///
403    /// let s = InlineString::from("hello");
404    /// assert_eq!(s.as_bytes(), [104, 101, 108, 108, 111]);
405    /// ```
406    #[inline]
407    pub fn as_bytes(&self) -> &[u8] {
408        self.assert_sanity();
409        &self.bytes[0..self.len()]
410    }
411
412    /// Shortens a string to the specified length.
413    ///
414    /// # Panics
415    ///
416    /// Panics if `new_len` > current length, or if `new_len` is not a character
417    /// boundary.
418    ///
419    /// # Examples
420    ///
421    /// ```
422    /// use inlinable_string::InlineString;
423    ///
424    /// let mut s = InlineString::from("hello");
425    /// s.truncate(2);
426    /// assert_eq!(s, "he");
427    /// ```
428    #[inline]
429    pub fn truncate(&mut self, new_len: usize) {
430        self.assert_sanity();
431
432        assert!(
433            self.is_char_boundary(new_len),
434            "inlinable_string::InlineString::truncate: new_len is not a character
435                 boundary"
436        );
437        assert!(new_len <= self.len());
438
439        self.length = new_len as u8;
440        self.assert_sanity();
441    }
442
443    /// Removes the last character from the string buffer and returns it.
444    /// Returns `None` if this string buffer is empty.
445    ///
446    /// # Examples
447    ///
448    /// ```
449    /// use inlinable_string::InlineString;
450    ///
451    /// let mut s = InlineString::from("foo");
452    /// assert_eq!(s.pop(), Some('o'));
453    /// assert_eq!(s.pop(), Some('o'));
454    /// assert_eq!(s.pop(), Some('f'));
455    /// assert_eq!(s.pop(), None);
456    /// ```
457    #[inline]
458    pub fn pop(&mut self) -> Option<char> {
459        self.assert_sanity();
460
461        match self.char_indices().rev().next() {
462            None => None,
463            Some((idx, ch)) => {
464                self.length = idx as u8;
465                self.assert_sanity();
466                Some(ch)
467            }
468        }
469    }
470
471    /// Removes the character from the string buffer at byte position `idx` and
472    /// returns it.
473    ///
474    /// # Panics
475    ///
476    /// If `idx` does not lie on a character boundary, or if it is out of
477    /// bounds, then this function will panic.
478    ///
479    /// # Examples
480    ///
481    /// ```
482    /// use inlinable_string::InlineString;
483    ///
484    /// let mut s = InlineString::from("foo");
485    /// assert_eq!(s.remove(0), 'f');
486    /// assert_eq!(s.remove(1), 'o');
487    /// assert_eq!(s.remove(0), 'o');
488    /// ```
489    #[inline]
490    pub fn remove(&mut self, idx: usize) -> char {
491        self.assert_sanity();
492        assert!(idx < self.len());
493
494        let ch = self
495            .get(idx..)
496            .expect(
497                "inlinable_string::InlineString::remove: idx does not lie on a
498                            character boundary",
499            )
500            .chars()
501            .next()
502            .expect("Should be `Some` because `idx < self.len()`");
503        let char_len = ch.len_utf8();
504        let next = idx + char_len;
505
506        unsafe {
507            let ptr = self.bytes.as_mut_ptr();
508
509            ptr::copy(
510                ptr.add(next),
511                ptr.add(idx),
512                self.len() - next,
513            );
514        }
515        self.length -= char_len as u8;
516
517        self.assert_sanity();
518        ch
519    }
520
521    /// Inserts the given bytes at the given position of the string.
522    unsafe fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) -> Result<(), NotEnoughSpaceError> {
523        let len = self.len();
524        let amt = bytes.len();
525
526        // This subtraction does not overflow because `INLINE_STRING_CAPACITY >= self.len()` holds.
527        if amt > INLINE_STRING_CAPACITY - len {
528            return Err(NotEnoughSpaceError);
529        }
530
531        let ptr = self.bytes.as_mut_ptr().add(idx);
532
533        // Shift the latter part.
534        ptr::copy(
535            ptr,
536            ptr.add(amt),
537            len - idx,
538        );
539        // Copy the bytes into the buffer.
540        ptr::copy(bytes.as_ptr(), self.bytes.as_mut_ptr().add(idx), amt);
541        // `amt` is less than `u8::MAX` becuase `INLINE_STRING_CAPACITY < u8::MAX` holds.
542        self.length += amt as u8;
543
544        Ok(())
545    }
546
547    /// Inserts a character into the string buffer at byte position `idx`.
548    ///
549    /// # Examples
550    ///
551    /// ```
552    /// use inlinable_string::InlineString;
553    ///
554    /// let mut s = InlineString::from("foo");
555    /// s.insert(2, 'f');
556    /// assert!(s == "fofo");
557    /// ```
558    ///
559    /// # Panics
560    ///
561    /// If `idx` does not lie on a character boundary or is out of bounds, then
562    /// this function will panic.
563    #[inline]
564    pub fn insert(&mut self, idx: usize, ch: char) -> Result<(), NotEnoughSpaceError> {
565        self.assert_sanity();
566        assert!(idx <= self.len());
567
568        let mut bits = [0; 4];
569        let bits = ch.encode_utf8(&mut bits).as_bytes();
570
571        unsafe {
572            self.insert_bytes(idx, bits)?;
573        }
574
575        self.assert_sanity();
576        Ok(())
577    }
578
579    /// Inserts a string into the string buffer at byte position `idx`.
580    ///
581    /// # Examples
582    ///
583    /// ```
584    /// use inlinable_string::InlineString;
585    ///
586    /// let mut s = InlineString::from("foo");
587    /// s.insert_str(2, "bar");
588    /// assert!(s == "fobaro");
589    /// ```
590    #[inline]
591    pub fn insert_str(&mut self, idx: usize, string: &str) -> Result<(), NotEnoughSpaceError> {
592        self.assert_sanity();
593        assert!(idx <= self.len());
594
595        unsafe {
596            self.insert_bytes(idx, string.as_bytes())?;
597        }
598
599        self.assert_sanity();
600        Ok(())
601    }
602
603    /// Views the internal string buffer as a mutable sequence of bytes.
604    ///
605    /// # Safety
606    ///
607    /// This is unsafe because it does not check to ensure that the resulting
608    /// string will be valid UTF-8.
609    ///
610    /// # Examples
611    ///
612    /// ```
613    /// use inlinable_string::InlineString;
614    ///
615    /// let mut s = InlineString::from("hello");
616    /// unsafe {
617    ///     let slice = s.as_mut_slice();
618    ///     assert!(slice == &[104, 101, 108, 108, 111]);
619    ///     slice.reverse();
620    /// }
621    /// assert_eq!(s, "olleh");
622    /// ```
623    #[inline]
624    pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
625        self.assert_sanity();
626        &mut self.bytes[0..self.length as usize]
627    }
628
629    /// Returns the number of bytes in this string.
630    ///
631    /// # Examples
632    ///
633    /// ```
634    /// use inlinable_string::InlineString;
635    ///
636    /// let a = InlineString::from("foo");
637    /// assert_eq!(a.len(), 3);
638    /// ```
639    #[inline]
640    pub fn len(&self) -> usize {
641        self.assert_sanity();
642        self.length as usize
643    }
644
645    /// Returns true if the string contains no bytes
646    ///
647    /// # Examples
648    ///
649    /// ```
650    /// use inlinable_string::InlineString;
651    ///
652    /// let mut v = InlineString::new();
653    /// assert!(v.is_empty());
654    /// v.push('a');
655    /// assert!(!v.is_empty());
656    /// ```
657    #[inline]
658    pub fn is_empty(&self) -> bool {
659        self.assert_sanity();
660        self.length == 0
661    }
662
663    /// Truncates the string, returning it to 0 length.
664    ///
665    /// # Examples
666    ///
667    /// ```
668    /// use inlinable_string::InlineString;
669    ///
670    /// let mut s = InlineString::from("foo");
671    /// s.clear();
672    /// assert!(s.is_empty());
673    /// ```
674    #[inline]
675    pub fn clear(&mut self) {
676        self.assert_sanity();
677        self.length = 0;
678        self.assert_sanity();
679    }
680}
681
682#[cfg(test)]
683mod tests {
684    use alloc::string::String;
685    use super::{InlineString, NotEnoughSpaceError, INLINE_STRING_CAPACITY};
686
687    #[test]
688    fn test_push_str() {
689        let mut s = InlineString::new();
690        assert!(s.push_str("small").is_ok());
691        assert_eq!(s, "small");
692
693        let long_str = "this is a really long string that is much larger than
694                        INLINE_STRING_CAPACITY and so cannot be stored inline.";
695        assert_eq!(s.push_str(long_str), Err(NotEnoughSpaceError));
696        assert_eq!(s, "small");
697    }
698
699    #[test]
700    fn test_push() {
701        let mut s = InlineString::new();
702
703        for _ in 0..INLINE_STRING_CAPACITY {
704            assert!(s.push('a').is_ok());
705        }
706
707        assert_eq!(s.push('a'), Err(NotEnoughSpaceError));
708    }
709
710    #[test]
711    fn test_insert() {
712        let mut s = InlineString::new();
713
714        for _ in 0..INLINE_STRING_CAPACITY {
715            assert!(s.insert(0, 'a').is_ok());
716        }
717
718        assert_eq!(s.insert(0, 'a'), Err(NotEnoughSpaceError));
719    }
720
721    #[test]
722    fn test_write() {
723        use core::fmt::{Error, Write};
724
725        let mut s = InlineString::new();
726        let mut normal_string = String::new();
727
728        for _ in 0..INLINE_STRING_CAPACITY {
729            assert!(write!(&mut s, "a").is_ok());
730            assert!(write!(&mut normal_string, "a").is_ok());
731        }
732
733        assert_eq!(write!(&mut s, "a"), Err(Error));
734        assert_eq!(&normal_string[..], &s[..]);
735    }
736}
737
738#[cfg(test)]
739#[cfg(feature = "nightly")]
740mod benches {
741    use test::Bencher;
742
743    #[bench]
744    fn its_fast(_b: &mut Bencher) {}
745}