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}