smart_string/smart_string/
mod.rs1use std::borrow::Borrow;
2use std::borrow::Cow;
3use std::cmp;
4use std::fmt;
5use std::hash::Hash;
6use std::hash::Hasher;
7use std::ops;
8use std::rc::Rc;
9use std::string::FromUtf16Error;
10use std::string::FromUtf8Error;
11use std::sync::Arc;
12
13use crate::pascal_string;
14use crate::DisplayExt;
15use crate::PascalString;
16
17#[cfg(feature = "serde")]
18mod with_serde;
19
20pub const DEFAULT_CAPACITY: usize = 30;
21
22#[derive(Clone)]
23pub enum SmartString<const N: usize = DEFAULT_CAPACITY> {
24 Heap(String),
25 Stack(PascalString<N>),
26}
27
28impl<const N: usize> SmartString<N> {
29 #[inline]
30 #[must_use]
31 pub const fn new() -> Self {
32 Self::Stack(PascalString::new())
33 }
34
35 #[cfg(not(no_global_oom_handling))]
36 #[inline]
37 #[must_use]
38 pub fn with_capacity(capacity: usize) -> Self {
39 if capacity <= N {
40 Self::new()
41 } else {
42 Self::Heap(String::with_capacity(capacity))
43 }
44 }
45
46 #[inline]
47 pub fn from_utf8(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
48 String::from_utf8(vec).map(Self::Heap)
49 }
50
51 #[cfg(not(no_global_oom_handling))]
63 pub fn from_utf16(v: &[u16]) -> Result<Self, FromUtf16Error> {
64 String::from_utf16(v).map(Self::Heap)
65 }
66
67 #[cfg(not(no_global_oom_handling))]
68 #[must_use]
69 #[inline]
70 pub fn from_utf16_lossy(v: &[u16]) -> Self {
71 Self::Heap(String::from_utf16_lossy(v))
72 }
73
74 #[inline]
75 #[must_use]
76 pub fn as_str(&self) -> &str {
77 self
78 }
79
80 #[inline]
81 #[must_use]
82 pub fn as_mut_str(&mut self) -> &mut str {
83 self
84 }
85
86 #[inline]
87 pub fn is_heap(&self) -> bool {
88 matches!(self, Self::Heap(_))
89 }
90
91 #[inline]
92 pub fn is_stack(&self) -> bool {
93 matches!(self, Self::Stack(_))
94 }
95
96 #[inline]
97 #[must_use]
98 pub fn into_heap(self) -> Self {
99 Self::Heap(match self {
100 Self::Stack(s) => s.to_string(),
101 Self::Heap(s) => s,
102 })
103 }
104
105 #[inline]
106 #[must_use]
107 pub fn try_into_stack(self) -> Self {
108 match self {
109 Self::Stack(s) => Self::Stack(s),
110 Self::Heap(s) => match PascalString::try_from(s.as_str()) {
111 Ok(s) => Self::Stack(s),
112 Err(pascal_string::TryFromStrError::TooLong) => Self::Heap(s),
113 },
114 }
115 }
116
117 #[inline]
118 pub fn push_str(&mut self, string: &str) {
119 match self {
120 Self::Heap(s) => s.push_str(string),
121 Self::Stack(s) => match s.try_push_str(string) {
122 Ok(()) => (),
123 Err(pascal_string::TryFromStrError::TooLong) => {
124 let mut new = String::with_capacity(s.len() + string.len());
125 new.push_str(s.as_str());
126 new.push_str(string);
127 *self = Self::Heap(new);
128 }
129 },
130 }
131 }
132
133 #[inline]
134 pub fn capacity(&self) -> usize {
135 match self {
136 Self::Heap(s) => s.capacity(),
137 Self::Stack(s) => s.capacity(),
138 }
139 }
140
141 #[cfg(not(no_global_oom_handling))]
142 #[inline]
143 pub fn reserve(&mut self, additional: usize) {
144 match self {
145 Self::Heap(s) => s.reserve(additional),
146 Self::Stack(s) => {
147 if s.capacity() - s.len() < additional {
148 let mut new = String::with_capacity(s.len() + additional);
149 new.push_str(s.as_str());
150 *self = Self::Heap(new);
151 }
152 }
153 }
154 }
155
156 #[cfg(not(no_global_oom_handling))]
157 pub fn reserve_exact(&mut self, additional: usize) {
158 match self {
159 Self::Heap(s) => s.reserve_exact(additional),
160 Self::Stack(s) => {
161 if s.capacity() - s.len() < additional {
162 let mut new = String::new();
163 new.reserve_exact(s.len() + additional);
164 new.push_str(s.as_str());
165 *self = Self::Heap(new);
166 }
167 }
168 }
169 }
170
171 #[rustversion::since(1.57)]
172 pub fn try_reserve(
173 &mut self,
174 additional: usize,
175 ) -> Result<(), std::collections::TryReserveError> {
176 match self {
177 Self::Heap(s) => s.try_reserve(additional),
178 Self::Stack(s) => {
179 if s.capacity() - s.len() < additional {
180 let mut new = String::new();
181 new.try_reserve(s.len() + additional)?;
182 new.push_str(s.as_str());
183 *self = Self::Heap(new);
184 }
185 Ok(())
186 }
187 }
188 }
189
190 #[rustversion::since(1.57)]
191 pub fn try_reserve_exact(
192 &mut self,
193 additional: usize,
194 ) -> Result<(), std::collections::TryReserveError> {
195 match self {
196 Self::Heap(s) => s.try_reserve_exact(additional),
197 Self::Stack(s) => {
198 if s.capacity() - s.len() < additional {
199 let mut new = String::new();
200 new.try_reserve_exact(s.len() + additional)?;
201 new.push_str(s.as_str());
202 *self = Self::Heap(new);
203 }
204 Ok(())
205 }
206 }
207 }
208
209 #[cfg(not(no_global_oom_handling))]
210 #[inline]
211 pub fn shrink_to_fit(&mut self) {
212 match self {
213 Self::Heap(s) => s.shrink_to_fit(),
214 Self::Stack(_) => (),
215 }
216 }
217
218 #[cfg(not(no_global_oom_handling))]
219 #[inline]
220 pub fn shrink_to(&mut self, min_capacity: usize) {
221 match self {
222 Self::Heap(s) => s.shrink_to(min_capacity),
223 Self::Stack(_) => (),
224 }
225 }
226
227 #[cfg(not(no_global_oom_handling))]
228 pub fn push(&mut self, ch: char) {
229 match self {
230 Self::Heap(s) => s.push(ch),
231 Self::Stack(s) => match s.try_push(ch) {
232 Ok(()) => (),
233 Err(pascal_string::TryFromStrError::TooLong) => {
234 let mut new = String::with_capacity(s.len() + ch.len_utf8());
235 new.push_str(s.as_str());
236 new.push(ch);
237 *self = Self::Heap(new);
238 }
239 },
240 }
241 }
242
243 #[inline]
244 pub fn truncate(&mut self, new_len: usize) {
245 match self {
246 Self::Heap(s) => s.truncate(new_len),
247 Self::Stack(s) => s.truncate(new_len),
248 }
249 }
250
251 #[inline]
252 pub fn pop(&mut self) -> Option<char> {
253 match self {
254 Self::Heap(s) => s.pop(),
255 Self::Stack(s) => s.pop(),
256 }
257 }
258
259 #[inline]
260 pub fn clear(&mut self) {
261 match self {
262 Self::Heap(s) => s.clear(),
263 Self::Stack(s) => s.clear(),
264 }
265 }
266}
267
268impl<const N: usize> Default for SmartString<N> {
271 #[inline]
272 fn default() -> Self {
273 Self::new()
274 }
275}
276
277impl<T: ops::Deref<Target = str> + ?Sized, const CAPACITY: usize> PartialEq<T>
278 for SmartString<CAPACITY>
279{
280 #[inline(always)]
281 fn eq(&self, other: &T) -> bool {
282 self.as_str().eq(other.deref())
283 }
284}
285
286macro_rules! impl_reverse_eq_for_str_types {
287 ($($t:ty),*) => {
288 $(
289 impl<const N: usize> PartialEq<SmartString<N>> for $t {
290 #[inline(always)]
291 fn eq(&self, other: &SmartString<N>) -> bool {
292 let a: &str = self.as_ref();
293 let b = other.as_str();
294 a.eq(b)
295 }
296 }
297
298 impl<const N: usize> PartialEq<SmartString<N>> for &$t {
299 #[inline(always)]
300 fn eq(&self, other: &SmartString<N>) -> bool {
301 let a: &str = self.as_ref();
302 let b = other.as_str();
303 a.eq(b)
304 }
305 }
306
307 impl<const N: usize> PartialEq<SmartString<N>> for &mut $t {
308 #[inline(always)]
309 fn eq(&self, other: &SmartString<N>) -> bool {
310 let a: &str = self.as_ref();
311 let b = other.as_str();
312 a.eq(b)
313 }
314 }
315 )*
316 };
317}
318
319impl_reverse_eq_for_str_types!(String, str, Cow<'_, str>, Box<str>, Rc<str>, Arc<str>);
320
321impl<const M: usize, const N: usize> PartialEq<SmartString<N>> for &PascalString<M> {
322 #[inline(always)]
323 fn eq(&self, other: &SmartString<N>) -> bool {
324 let a: &str = self.as_ref();
325 let b = other.as_str();
326 a.eq(b)
327 }
328}
329
330impl<const M: usize, const N: usize> PartialEq<SmartString<N>> for &mut PascalString<M> {
331 #[inline(always)]
332 fn eq(&self, other: &SmartString<N>) -> bool {
333 let a: &str = self.as_ref();
334 let b = other.as_str();
335 a.eq(b)
336 }
337}
338
339impl<const N: usize> Eq for SmartString<N> {}
340
341impl<T: ops::Deref<Target = str>, const N: usize> PartialOrd<T> for SmartString<N> {
342 #[inline(always)]
343 fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
344 self.as_str().partial_cmp(other.deref())
345 }
346}
347
348impl<const N: usize> Ord for SmartString<N> {
349 #[inline(always)]
350 fn cmp(&self, other: &Self) -> cmp::Ordering {
351 self.as_str().cmp(other.as_str())
352 }
353}
354
355impl<const N: usize> Hash for SmartString<N> {
356 #[inline(always)]
357 fn hash<H: Hasher>(&self, state: &mut H) {
358 self.as_str().hash(state)
359 }
360}
361
362impl<const N: usize> fmt::Debug for SmartString<N> {
365 #[inline(always)]
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 let name: PascalString<39> = format_args!("SmartString<{N}>")
368 .try_to_fmt()
369 .unwrap_or_else(|_| "SmartString<?>".to_fmt());
370 f.debug_tuple(&name).field(&self.as_str()).finish()
371 }
372}
373
374impl<const N: usize> fmt::Display for SmartString<N> {
375 #[inline]
376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 match self {
378 Self::Heap(s) => s.fmt(f),
379 Self::Stack(s) => s.fmt(f),
380 }
381 }
382}
383
384impl<const N: usize> ops::Deref for SmartString<N> {
387 type Target = str;
388
389 #[inline]
390 fn deref(&self) -> &Self::Target {
391 match self {
392 Self::Heap(s) => s.deref(),
393 Self::Stack(s) => s.deref(),
394 }
395 }
396}
397
398impl<const N: usize> ops::DerefMut for SmartString<N> {
399 #[inline]
400 fn deref_mut(&mut self) -> &mut Self::Target {
401 match self {
402 Self::Heap(s) => s.deref_mut(),
403 Self::Stack(s) => s.deref_mut(),
404 }
405 }
406}
407
408impl<const N: usize> Borrow<str> for SmartString<N> {
409 #[inline(always)]
410 fn borrow(&self) -> &str {
411 self
412 }
413}
414
415impl<const N: usize> AsRef<str> for SmartString<N> {
416 #[inline(always)]
417 fn as_ref(&self) -> &str {
418 self
419 }
420}
421
422impl<const N: usize> AsRef<[u8]> for SmartString<N> {
423 #[inline(always)]
424 fn as_ref(&self) -> &[u8] {
425 self.as_bytes()
426 }
427}
428
429impl<const N: usize> From<String> for SmartString<N> {
432 #[inline]
433 fn from(s: String) -> Self {
434 Self::Heap(s)
435 }
436}
437
438impl<const M: usize, const N: usize> From<PascalString<M>> for SmartString<N> {
439 #[inline]
440 fn from(s: PascalString<M>) -> Self {
441 PascalString::try_from(s.as_str())
442 .map(Self::Stack)
443 .unwrap_or_else(|pascal_string::TryFromStrError::TooLong| Self::Heap(s.to_string()))
444 }
445}
446
447impl<const N: usize> From<&str> for SmartString<N> {
448 #[inline]
449 fn from(s: &str) -> Self {
450 PascalString::try_from(s)
451 .map(Self::Stack)
452 .unwrap_or_else(|pascal_string::TryFromStrError::TooLong| Self::Heap(String::from(s)))
453 }
454}
455
456impl<const N: usize> From<&mut str> for SmartString<N> {
457 #[inline]
458 fn from(s: &mut str) -> Self {
459 Self::from(&*s)
460 }
461}
462
463impl<const N: usize> From<Cow<'_, str>> for SmartString<N> {
464 #[inline]
465 fn from(s: Cow<'_, str>) -> Self {
466 match s {
467 Cow::Borrowed(s) => Self::from(s),
468 Cow::Owned(s) => Self::Heap(s),
469 }
470 }
471}
472
473impl<const N: usize> FromIterator<char> for SmartString<N> {
474 fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
475 let mut s = Self::new();
476 for ch in iter {
478 s.push(ch);
479 }
480 s
481 }
482}
483
484impl<'a, const N: usize> FromIterator<&'a str> for SmartString<N> {
485 fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
486 let mut s = Self::new();
487 for string in iter {
489 s.push_str(string);
490 }
491 s
492 }
493}
494
495impl<const N: usize> fmt::Write for SmartString<N> {
498 #[inline]
499 fn write_str(&mut self, s: &str) -> fmt::Result {
500 Ok(self.push_str(s))
501 }
502}
503
504impl<const N: usize, T: ops::Deref<Target = str>> ops::Add<T> for SmartString<N> {
507 type Output = Self;
508
509 #[inline]
510 fn add(mut self, rhs: T) -> Self::Output {
511 self.push_str(&*rhs);
512 self
513 }
514}
515
516#[cfg(test)]
519mod tests {
520 use std::mem;
521
522 use super::*;
523
524 #[test]
525 fn test_size() {
526 assert_eq!(mem::size_of::<SmartString>(), 32);
528
529 assert_eq!(mem::size_of::<SmartString<0>>(), 24);
532 assert_eq!(mem::size_of::<SmartString<1>>(), 24);
533 assert_eq!(mem::size_of::<SmartString<15>>(), 24);
534
535 assert_eq!(mem::size_of::<SmartString<16>>(), 32);
538 assert_eq!(mem::size_of::<SmartString<22>>(), 32);
539
540 assert_eq!(mem::size_of::<SmartString<23>>(), 32);
542 assert_eq!(mem::size_of::<SmartString<30>>(), 32);
543
544 assert_eq!(mem::size_of::<SmartString<31>>(), 40);
547 assert_eq!(mem::size_of::<SmartString<38>>(), 40);
548
549 assert_eq!(mem::size_of::<SmartString<39>>(), 48);
550 assert_eq!(mem::size_of::<SmartString<46>>(), 48);
551 }
552}