flat_string/
lib.rs

1#[cfg(test)]
2mod tests;
3
4use std::ops::Deref;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7pub struct FlatString<const SIZE: usize = 14> {
8    data: [u8; SIZE],
9    len: u8,
10    chars: u8,
11}
12impl<const SIZE: usize> FlatString<SIZE> {
13    /// Create a new FlatString with a fixed size
14    ///
15    /// # Panics
16    /// - If SIZE is 0 or greater than 255
17    ///
18    /// # Example
19    /// ```rust
20    /// use flat_string::FlatString;
21    /// let s = FlatString::<10>::new();
22    /// ```
23    pub fn new() -> Self {
24        assert!(SIZE > 0, "SIZE must be greater than 0");
25        assert!(SIZE < 256, "SIZE must be less than 256");
26        Self {
27            data: [0; SIZE],
28            len: 0,
29            chars: 0,
30        }
31    }
32
33    /// Create a new FlatString from a string slice
34    /// If the string slice is larger than the available space, only the first characters that fit will be copied
35    ///
36    /// # Panics
37    /// - If SIZE is 0 or greater than 255
38    ///
39    /// # Example
40    /// ```rust
41    /// use flat_string::FlatString;
42    /// let s = FlatString::<10>::from_str("Hello");
43    /// ```
44    pub fn from_str(text: &str) -> Self {
45        let mut this = Self::new();
46        this.push_str(text);
47        this
48    }
49
50    /// Clears the content of the FlatString. This operation does not deallocate the memory, not it does not clear the content o the string. It only resets the length and characters count to 0.
51    #[inline(always)]
52    pub fn clear(&mut self) {
53        self.len = 0;
54        self.chars = 0;
55    }
56
57    /// Returns the length of the string in bytes. This operation is performed in O(1) time.
58    #[inline(always)]
59    pub fn len(&self) -> usize {
60        self.len as usize
61    }
62
63    /// Returns true if the string is empty, false otherwise. This operation is performed in O(1) time.
64    #[inline(always)]
65    pub fn is_empty(&self) -> bool {
66        self.len == 0
67    }
68
69    /// Returns the number of characters in the string. This operation is performed in O(1) time.
70    #[inline(always)]
71    pub fn chars_count(&self) -> usize {
72        self.chars as usize
73    }
74
75    /// Returns the capacity of the FlatString. This operation is performed in O(1) time.
76    #[inline(always)]
77    pub fn capacity(&self) -> usize {
78        SIZE
79    }
80    // copy the string only if it fits the available space or return false otherwise
81    fn add_entire_string(&mut self, text: &str) -> bool {
82        let len = text.len();
83        if len + self.len as usize <= SIZE {
84            self.data[self.len as usize..self.len as usize + len].copy_from_slice(text.as_bytes());
85            self.len += len as u8;
86            self.chars += text.chars().count() as u8;
87            true
88        } else {
89            false
90        }
91    }
92    // fills the buffer with as much characters as possible
93    fn fill_with_str(&mut self, text: &str) {
94        let mut poz = 0;
95        let mut count_chars = 0;
96        let len = self.len as usize;
97        for (i, _) in text.char_indices() {
98            if i + len > SIZE {
99                break;
100            }
101            poz = i;
102            count_chars += 1;
103        }
104        // we count the number of characters that fit in the buffer
105        // the first character is already counted (as its pozition will be 0)
106        if count_chars > 1 {
107            let bytes = text[..poz].as_bytes();
108            self.data[len..len + poz].copy_from_slice(bytes);
109            self.len += poz as u8;
110            self.chars += (count_chars - 1) as u8;
111        }
112    }
113
114    /// Appends a string slice to the FlatString. If the string slice is larger than the available space, only the first characters that fit will be copied.
115    ///
116    /// # Example
117    /// ```rust
118    /// use flat_string::FlatString;
119    /// let mut s = FlatString::<10>::new();
120    /// s.push_str("Hello");
121    /// ```
122    #[inline(always)]
123    pub fn push_str(&mut self, text: &str) {
124        // try the fast method first
125        if !self.add_entire_string(text) {
126            // if it fails, copy as much characters as possible
127            self.fill_with_str(text);
128        }
129    }
130
131    /// Appends a character to the FlatString. If the character does not fit in the available space, it will not be copied.
132    ///
133    /// # Example
134    /// ```rust
135    /// use flat_string::FlatString;
136    /// let mut s = FlatString::<10>::new();
137    /// s.push('H');
138    /// ```
139    #[inline(always)]
140    pub fn push(&mut self, c: char) {
141        let mut bytes = [0; 8];
142        self.push_str(c.encode_utf8(&mut bytes));
143    }
144
145    /// Tries to append a string slice to the FlatString. If the string slice can fit in the available space, it will be copied and Some(&str) will be returned. Otherwise, None will be returned and ths string will remain unchanged.
146    ///
147    /// # Example
148    /// ```rust
149    /// use flat_string::FlatString;
150    /// let mut s = FlatString::<10>::new();
151    /// assert_eq!(s.try_push_str("Hello"), Some("Hello"));
152    /// assert_eq!(s.try_push_str(" Wor"), Some("Hello Wor"));
153    /// assert_eq!(s.try_push_str("ld !"), None); // the string does not fit
154    /// ```
155    #[inline(always)]
156    pub fn try_push_str(&mut self, text: &str) -> Option<&str> {
157        if self.add_entire_string(text) {
158            Some(self.as_str())
159        } else {
160            None
161        }
162    }
163
164    /// Tries to append a character to the FlatString. If the character can fit in the available space, it will be copied and Some(&str) will be returned. Otherwise, None will be returned and ths string will remain unchanged.
165    ///
166    /// # Example
167    /// ```rust
168    /// use flat_string::FlatString;
169    /// let mut s = FlatString::<5>::new();
170    /// assert_eq!(s.try_push('H'), Some("H"));
171    /// assert_eq!(s.try_push('e'), Some("He"));
172    /// assert_eq!(s.try_push('l'), Some("Hel"));
173    /// assert_eq!(s.try_push('l'), Some("Hell"));
174    /// assert_eq!(s.try_push('o'), Some("Hello"));
175    /// assert_eq!(s.try_push('!'), None); // the character does not fit
176    /// ```
177    #[inline(always)]
178    pub fn try_push(&mut self, c: char) -> Option<&str> {
179        let mut bytes = [0; 8];
180        if self.add_entire_string(c.encode_utf8(&mut bytes)) {
181            Some(self.as_str())
182        } else {
183            None
184        }
185    }
186
187    /// Sets the content of the FlatString to a string slice. If the string slice is larger than the available space, only the first characters that fit will be copied.
188    ///
189    /// # Example
190    /// ```rust
191    /// use flat_string::FlatString;
192    /// let mut s = FlatString::<10>::new();
193    /// s.set("Hello");
194    /// ```
195    #[inline(always)]
196    pub fn set(&mut self, text: &str) {
197        self.clear();
198        self.push_str(text);
199    }
200
201    /// Returns the content of the FlatString as a string slice. This operation is performed in O(1) time.
202    ///
203    /// # Example
204    /// ```rust
205    /// use flat_string::FlatString;
206    /// let s = FlatString::<10>::from_str("Hello");
207    /// assert_eq!(s.as_str(), "Hello");
208    /// ```
209    #[inline(always)]
210    pub fn as_str(&self) -> &str {
211        if self.len == 0 {
212            ""
213        } else {
214            unsafe { std::str::from_utf8_unchecked(&self.data[..self.len as usize]) }
215        }
216    }
217
218    /// Truncates this FlatString to the specified length.
219    ///
220    /// If new_len is greater than or equal to the string’s current length, this has no effect.
221    ///
222    /// # Panics
223    /// Panics if new_len does not lie on a char boundary.
224    pub fn truncate(&mut self, new_len: usize) {
225        if new_len >= self.len as usize {
226            return;
227        }
228        let p = &(self.as_str())[..new_len];
229        self.chars = p.chars().count() as u8;
230        self.len = new_len as u8;
231    }
232
233    /// Removes the last character from the string buffer and returns it.
234    /// Returns None if this String is empty.
235    pub fn pop(&mut self) -> Option<char> {
236        if self.chars > 0 {
237            if let Some(ch) = self.chars().last() {
238                assert!(self.len >= ch.len_utf8() as u8);
239                self.chars -= 1;
240                self.len -= ch.len_utf8() as u8;
241                return Some(ch);
242            }
243        }
244        None
245    }
246
247    /// Inserts a string slice into this FlatString at a byte position.
248    ///
249    /// #Panics
250    ///
251    /// Panics if idx is larger than the FlatString’s length, or if it does not lie on a char boundary.
252    ///
253    pub fn insert(&mut self, index: usize, text: &str) {
254        self.insert_fast(index, text);
255    }
256
257    fn insert_fast(&mut self, index: usize, text: &str) {
258        if index + text.len() < SIZE {
259            // there is room to shift all or a part of the existing text
260            self.rshift(index, text.len());
261        }
262        self.write_at(index, text);
263        self.chars = self
264            .walk_string(
265                std::str::from_utf8(&self.data[..self.len as usize]).unwrap(),
266                0,
267                SIZE,
268            )
269            .1 as u8;
270    }
271
272    fn write_at(&mut self, index: usize, text: &str) {
273        let (poz, count_chars) = self.walk_string(text, index, SIZE);
274
275        if count_chars > 0 {
276            let bytes = text[..poz].as_bytes();
277            self.data[index..index + poz].copy_from_slice(bytes);
278            // increase len if the written text exceeds original length
279            self.len = self.len.max((index + poz) as u8);
280        }
281    }
282
283    fn rshift(&mut self, index: usize, shift_size: usize) {
284        let dst_start = index + shift_size;
285        let str_to_shift = std::str::from_utf8(&self.data[index..self.len as usize]).unwrap();
286        let max_bytes_to_shift = str_to_shift.len();
287
288        let (no_bytes_to_shift, no_chars_to_shift) =
289            self.walk_string(str_to_shift, dst_start, SIZE);
290
291        if no_chars_to_shift > 0 {
292            self.data
293                .copy_within(index..index + no_bytes_to_shift, dst_start);
294
295            // Adjust by the actual number of bytes added or removed.
296            // Removal is possibie when a unicode character cannot be
297            // entirely copied.
298            self.len += (shift_size + no_bytes_to_shift) as u8;
299            self.len -= max_bytes_to_shift as u8;
300        }
301    }
302
303    fn walk_string(&self, text: &str, start_index: usize, max_size: usize) -> (usize, usize) {
304        let mut no_bytes = 0;
305        let mut no_chars = 0;
306        for (_, c) in text.char_indices() {
307            if start_index + no_bytes + c.len_utf8() > max_size {
308                break;
309            }
310            no_bytes += c.len_utf8();
311            no_chars += 1;
312        }
313        (no_bytes, no_chars)
314    }
315
316    /// Inserts a character into this FlatString at a byte position.
317    ///
318    /// #Panics
319    ///
320    /// Panics if idx is larger than the FlatString’s length, or if it does not lie on a char boundary.
321    pub fn insert_char(&mut self, idx: usize, ch: char) {
322        let mut bytes = [0; 8];
323        self.insert(idx, ch.encode_utf8(&mut bytes));
324    }
325
326    /// Removes a char from this FlatString at a byte position and returns it.
327    ///
328    /// # Panics
329    ///
330    /// Panics if idx is larger than or equal to the FlatString’s length, or if it does not lie on a char boundary.
331    pub fn remove(&mut self, idx: usize) -> char {
332        assert!(idx < self.len as usize);
333
334        let conv = std::str::from_utf8(&self.data[idx..]);
335        assert!(conv.is_ok());
336        let ch_opt = conv.unwrap().chars().next();
337        assert!(ch_opt.is_some());
338        let ch = ch_opt.unwrap();
339
340        let next_char_as_byte_index = idx + ch.len_utf8();
341        if next_char_as_byte_index < SIZE {
342            self.data.copy_within(next_char_as_byte_index.., idx);
343        }
344        self.len -= ch.len_utf8() as u8;
345        self.chars -= 1;
346        ch
347    }
348}
349
350impl<const SIZE: usize> Deref for FlatString<SIZE> {
351    type Target = str;
352    fn deref(&self) -> &Self::Target {
353        self.as_str()
354    }
355}
356
357impl<const SIZE: usize> Default for FlatString<SIZE> {
358    fn default() -> Self {
359        Self::new()
360    }
361}
362
363impl<const SIZE: usize> std::fmt::Display for FlatString<SIZE> {
364    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
365        self.as_str().fmt(f)
366    }
367}