1use alloc::borrow::Cow;
2use alloc::string::{ParseError, String, ToString};
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5use core::borrow::Borrow;
6use core::cmp::Ordering;
7use core::fmt::{self, Display};
8use core::hash::{Hash, Hasher};
9use core::ops::{Add, AddAssign, Deref};
10use core::str::{self, FromStr, Utf8Error};
11
12#[derive(Clone)]
13#[repr(transparent)]
14pub struct CheetahString {
15 pub(super) inner: InnerString,
16}
17
18impl Default for CheetahString {
19 fn default() -> Self {
20 CheetahString {
21 inner: InnerString::Inline {
22 len: 0,
23 data: [0; INLINE_CAPACITY],
24 },
25 }
26 }
27}
28
29impl From<String> for CheetahString {
30 #[inline]
31 fn from(s: String) -> Self {
32 CheetahString::from_string(s)
33 }
34}
35
36impl From<Arc<String>> for CheetahString {
37 #[inline]
38 fn from(s: Arc<String>) -> Self {
39 CheetahString::from_arc_string(s)
40 }
41}
42
43impl<'a> From<&'a str> for CheetahString {
44 #[inline]
45 fn from(s: &'a str) -> Self {
46 CheetahString::from_slice(s)
47 }
48}
49
50impl<'a> TryFrom<&'a [u8]> for CheetahString {
51 type Error = Utf8Error;
52
53 #[inline]
54 fn try_from(b: &'a [u8]) -> Result<Self, Self::Error> {
55 CheetahString::try_from_bytes(b)
56 }
57}
58
59impl FromStr for CheetahString {
60 type Err = ParseError;
61 #[inline]
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 Ok(CheetahString::from_slice(s))
64 }
65}
66
67impl TryFrom<Vec<u8>> for CheetahString {
68 type Error = Utf8Error;
69
70 #[inline]
71 fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
72 CheetahString::try_from_vec(v)
73 }
74}
75
76impl From<Cow<'static, str>> for CheetahString {
77 #[inline]
78 fn from(cow: Cow<'static, str>) -> Self {
79 match cow {
80 Cow::Borrowed(s) => CheetahString::from_static_str(s),
81 Cow::Owned(s) => CheetahString::from_string(s),
82 }
83 }
84}
85
86impl From<Cow<'_, String>> for CheetahString {
87 #[inline]
88 fn from(cow: Cow<'_, String>) -> Self {
89 match cow {
90 Cow::Borrowed(s) => CheetahString::from_slice(s),
91 Cow::Owned(s) => CheetahString::from_string(s),
92 }
93 }
94}
95
96impl From<char> for CheetahString {
97 #[inline]
107 fn from(c: char) -> Self {
108 CheetahString::from_string(c.to_string())
109 }
110}
111
112impl<'a> FromIterator<&'a char> for CheetahString {
113 #[inline]
114 fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> CheetahString {
115 let mut buf = String::new();
116 buf.extend(iter);
117 CheetahString::from_string(buf)
118 }
119}
120
121impl<'a> FromIterator<&'a str> for CheetahString {
122 fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> CheetahString {
123 let mut buf = String::new();
124 buf.extend(iter);
125 CheetahString::from_string(buf)
126 }
127}
128
129impl FromIterator<String> for CheetahString {
130 #[inline]
131 fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
132 let mut buf = String::new();
133 buf.extend(iter);
134 CheetahString::from_string(buf)
135 }
136}
137
138impl<'a> FromIterator<&'a String> for CheetahString {
139 #[inline]
140 fn from_iter<T: IntoIterator<Item = &'a String>>(iter: T) -> Self {
141 let mut buf = String::new();
142 buf.extend(iter.into_iter().map(|s| s.as_str()));
143 CheetahString::from_string(buf)
144 }
145}
146
147#[cfg(feature = "bytes")]
148impl TryFrom<bytes::Bytes> for CheetahString {
149 type Error = Utf8Error;
150
151 #[inline]
152 fn try_from(b: bytes::Bytes) -> Result<Self, Self::Error> {
153 CheetahString::try_from_bytes_buf(b)
154 }
155}
156
157impl From<&CheetahString> for CheetahString {
158 #[inline]
159 fn from(s: &CheetahString) -> Self {
160 s.clone()
161 }
162}
163
164impl From<CheetahString> for String {
165 #[inline]
166 fn from(s: CheetahString) -> Self {
167 match s {
168 CheetahString {
169 inner: InnerString::Inline { len, data },
170 } => {
171 unsafe { String::from_utf8_unchecked(data[..len as usize].to_vec()) }
173 }
174 CheetahString {
175 inner: InnerString::Static(s),
176 } => s.to_string(),
177 CheetahString {
178 inner: InnerString::Shared(s),
179 } => s.to_string(),
180 CheetahString {
181 inner: InnerString::Owned(s),
182 } => s,
183 }
184 }
185}
186
187impl Deref for CheetahString {
188 type Target = str;
189
190 #[inline]
191 fn deref(&self) -> &Self::Target {
192 self.as_str()
193 }
194}
195
196impl AsRef<str> for CheetahString {
197 #[inline]
198 fn as_ref(&self) -> &str {
199 self.as_str()
200 }
201}
202
203impl AsRef<[u8]> for CheetahString {
204 #[inline]
205 fn as_ref(&self) -> &[u8] {
206 self.as_bytes()
207 }
208}
209
210impl AsRef<CheetahString> for CheetahString {
211 #[inline]
212 fn as_ref(&self) -> &CheetahString {
213 self
214 }
215}
216
217impl From<&String> for CheetahString {
218 #[inline]
219 fn from(s: &String) -> Self {
220 CheetahString::from_slice(s)
221 }
222}
223
224impl CheetahString {
225 #[inline]
226 pub const fn empty() -> Self {
227 CheetahString {
228 inner: InnerString::Inline {
229 len: 0,
230 data: [0; INLINE_CAPACITY],
231 },
232 }
233 }
234
235 #[inline]
236 pub fn new() -> Self {
237 CheetahString::default()
238 }
239
240 #[inline]
241 pub const fn from_static_str(s: &'static str) -> Self {
242 CheetahString {
243 inner: InnerString::Static(s),
244 }
245 }
246
247 #[deprecated(
248 since = "1.1.0",
249 note = "use try_from_vec for checked construction or from_utf8_unchecked_vec for an explicit unsafe constructor"
250 )]
251 pub fn from_vec(s: Vec<u8>) -> Self {
252 CheetahString::try_from_vec(s).expect(
253 "CheetahString::from_vec requires valid UTF-8; use try_from_vec for fallible construction",
254 )
255 }
256
257 #[inline]
264 pub unsafe fn from_utf8_unchecked_vec(s: Vec<u8>) -> Self {
265 CheetahString::from_validated_vec_unchecked(s)
266 }
267
268 #[inline]
269 fn from_validated_vec_unchecked(s: Vec<u8>) -> Self {
270 if s.len() <= INLINE_CAPACITY {
271 let mut data = [0u8; INLINE_CAPACITY];
272 data[..s.len()].copy_from_slice(&s);
273 CheetahString {
274 inner: InnerString::Inline {
275 len: s.len() as u8,
276 data,
277 },
278 }
279 } else {
280 CheetahString::from_builder_string(unsafe { String::from_utf8_unchecked(s) })
282 }
283 }
284
285 pub fn try_from_vec(v: Vec<u8>) -> Result<Self, Utf8Error> {
304 str::from_utf8(&v)?;
305 Ok(CheetahString::from_validated_vec_unchecked(v))
306 }
307
308 pub fn try_from_bytes(b: &[u8]) -> Result<Self, Utf8Error> {
327 let s = str::from_utf8(b)?;
328 Ok(CheetahString::from_slice(s))
329 }
330
331 #[inline]
337 pub unsafe fn from_utf8_unchecked_bytes(b: &[u8]) -> Self {
338 CheetahString::from_slice(unsafe { str::from_utf8_unchecked(b) })
340 }
341
342 #[inline]
348 pub fn try_from_arc_vec(s: Arc<Vec<u8>>) -> Result<Self, Utf8Error> {
349 match Arc::try_unwrap(s) {
350 Ok(v) => CheetahString::try_from_vec(v),
351 Err(s) => {
352 let s = str::from_utf8(s.as_slice())?;
353 Ok(CheetahString::from_slice(s))
354 }
355 }
356 }
357
358 #[deprecated(
359 since = "1.1.0",
360 note = "use try_from_arc_vec for checked construction or from_utf8_unchecked_arc_vec for an explicit unsafe constructor"
361 )]
362 #[inline]
363 pub fn from_arc_vec(s: Arc<Vec<u8>>) -> Self {
364 CheetahString::try_from_arc_vec(s).expect(
365 "CheetahString::from_arc_vec requires valid UTF-8; use try_from_arc_vec for fallible construction",
366 )
367 }
368
369 #[inline]
375 pub unsafe fn from_utf8_unchecked_arc_vec(s: Arc<Vec<u8>>) -> Self {
376 CheetahString::from_validated_arc_vec_unchecked(s)
377 }
378
379 #[inline]
380 fn from_validated_arc_vec_unchecked(s: Arc<Vec<u8>>) -> Self {
381 match Arc::try_unwrap(s) {
382 Ok(v) => CheetahString::from_validated_vec_unchecked(v),
383 Err(s) => {
384 unsafe { CheetahString::from_utf8_unchecked_bytes(s.as_slice()) }
386 }
387 }
388 }
389
390 #[inline]
391 pub fn from_slice(s: &str) -> Self {
392 if s.len() <= INLINE_CAPACITY {
393 let mut data = [0u8; INLINE_CAPACITY];
395 data[..s.len()].copy_from_slice(s.as_bytes());
396 CheetahString {
397 inner: InnerString::Inline {
398 len: s.len() as u8,
399 data,
400 },
401 }
402 } else {
403 let arc_str: Arc<str> = Arc::from(s);
405 CheetahString {
406 inner: InnerString::Shared(arc_str),
407 }
408 }
409 }
410
411 #[inline]
412 pub fn from_string(s: String) -> Self {
413 CheetahString::from_string_shared(s)
414 }
415
416 #[inline]
423 pub fn from_string_owned(s: String) -> Self {
424 CheetahString::from_builder_string(s)
425 }
426
427 #[inline]
434 pub fn from_string_shared(s: String) -> Self {
435 if s.len() <= INLINE_CAPACITY {
436 let mut data = [0u8; INLINE_CAPACITY];
438 data[..s.len()].copy_from_slice(s.as_bytes());
439 CheetahString {
440 inner: InnerString::Inline {
441 len: s.len() as u8,
442 data,
443 },
444 }
445 } else {
446 let arc_str: Arc<str> = s.into_boxed_str().into();
448 CheetahString {
449 inner: InnerString::Shared(arc_str),
450 }
451 }
452 }
453
454 #[inline]
455 fn from_builder_string(s: String) -> Self {
456 if s.len() <= INLINE_CAPACITY && s.capacity() <= INLINE_CAPACITY {
457 let mut data = [0u8; INLINE_CAPACITY];
458 data[..s.len()].copy_from_slice(s.as_bytes());
459 CheetahString {
460 inner: InnerString::Inline {
461 len: s.len() as u8,
462 data,
463 },
464 }
465 } else {
466 CheetahString {
467 inner: InnerString::Owned(s),
468 }
469 }
470 }
471
472 #[inline]
473 pub fn from_arc_string(s: Arc<String>) -> Self {
474 match Arc::try_unwrap(s) {
475 Ok(s) => CheetahString::from_builder_string(s),
476 Err(s) => CheetahString::from_slice(s.as_str()),
477 }
478 }
479
480 #[inline]
481 #[cfg(feature = "bytes")]
482 #[deprecated(
483 since = "1.1.0",
484 note = "use try_from_bytes_buf for checked construction or from_utf8_unchecked_bytes_buf for an explicit unsafe constructor"
485 )]
486 pub fn from_bytes(b: bytes::Bytes) -> Self {
487 CheetahString::try_from_bytes_buf(b).expect(
488 "CheetahString::from_bytes requires valid UTF-8; use try_from_bytes_buf for fallible construction",
489 )
490 }
491
492 #[inline]
493 #[cfg(feature = "bytes")]
494 pub fn try_from_bytes_buf(b: bytes::Bytes) -> Result<Self, Utf8Error> {
495 str::from_utf8(b.as_ref())?;
496 Ok(CheetahString::from_validated_bytes_unchecked(b))
497 }
498
499 #[inline]
505 #[cfg(feature = "bytes")]
506 pub unsafe fn from_utf8_unchecked_bytes_buf(b: bytes::Bytes) -> Self {
507 CheetahString::from_validated_bytes_unchecked(b)
508 }
509
510 #[inline]
511 #[cfg(feature = "bytes")]
512 fn from_validated_bytes_unchecked(b: bytes::Bytes) -> Self {
513 unsafe { CheetahString::from_utf8_unchecked_bytes(b.as_ref()) }
515 }
516
517 #[inline]
518 pub fn as_str(&self) -> &str {
519 match &self.inner {
520 InnerString::Inline { len, data } => {
521 unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
524 }
525 InnerString::Static(s) => s,
526 InnerString::Shared(s) => s.as_ref(),
527 InnerString::Owned(s) => s.as_str(),
528 }
529 }
530
531 #[inline]
532 pub fn as_bytes(&self) -> &[u8] {
533 match &self.inner {
534 InnerString::Inline { len, data } => &data[..*len as usize],
535 InnerString::Static(s) => s.as_bytes(),
536 InnerString::Shared(s) => s.as_bytes(),
537 InnerString::Owned(s) => s.as_bytes(),
538 }
539 }
540
541 #[inline]
542 pub fn len(&self) -> usize {
543 match &self.inner {
544 InnerString::Inline { len, .. } => *len as usize,
545 InnerString::Static(s) => s.len(),
546 InnerString::Shared(s) => s.len(),
547 InnerString::Owned(s) => s.len(),
548 }
549 }
550
551 #[inline]
552 pub fn is_empty(&self) -> bool {
553 match &self.inner {
554 InnerString::Inline { len, .. } => *len == 0,
555 InnerString::Static(s) => s.is_empty(),
556 InnerString::Shared(s) => s.is_empty(),
557 InnerString::Owned(s) => s.is_empty(),
558 }
559 }
560
561 #[inline]
579 pub fn starts_with<P: StrPattern>(&self, pat: P) -> bool {
580 match pat.as_str_pattern() {
581 StrPatternImpl::Char(c) => self.as_str().starts_with(c),
582 StrPatternImpl::Str(s) => {
583 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
584 {
585 if s.len() >= crate::simd::SIMD_THRESHOLD {
586 return crate::simd::starts_with_bytes(self.as_bytes(), s.as_bytes());
587 }
588 }
589
590 self.as_str().starts_with(s)
591 }
592 }
593 }
594
595 #[inline]
607 pub fn starts_with_char(&self, pat: char) -> bool {
608 self.as_str().starts_with(pat)
609 }
610
611 #[inline]
627 pub fn ends_with<P: StrPattern>(&self, pat: P) -> bool {
628 match pat.as_str_pattern() {
629 StrPatternImpl::Char(c) => self.as_str().ends_with(c),
630 StrPatternImpl::Str(s) => {
631 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
632 {
633 if s.len() >= crate::simd::SIMD_THRESHOLD {
634 return crate::simd::ends_with_bytes(self.as_bytes(), s.as_bytes());
635 }
636 }
637
638 self.as_str().ends_with(s)
639 }
640 }
641 }
642
643 #[inline]
655 pub fn ends_with_char(&self, pat: char) -> bool {
656 self.as_str().ends_with(pat)
657 }
658
659 #[inline]
675 pub fn contains<P: StrPattern>(&self, pat: P) -> bool {
676 match pat.as_str_pattern() {
677 StrPatternImpl::Char(c) => self.as_str().contains(c),
678 StrPatternImpl::Str(s) => {
679 crate::search::find_bytes(self.as_bytes(), s.as_bytes()).is_some()
680 }
681 }
682 }
683
684 #[inline]
696 pub fn contains_char(&self, pat: char) -> bool {
697 self.as_str().contains(pat)
698 }
699
700 #[inline]
715 pub fn find<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
716 let pat = pat.as_ref();
717 crate::search::find_bytes(self.as_bytes(), pat.as_bytes())
718 }
719
720 #[inline]
731 pub fn rfind<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
732 crate::search::rfind_bytes(self.as_bytes(), pat.as_ref().as_bytes())
733 }
734
735 #[inline]
746 pub fn trim(&self) -> &str {
747 self.as_str().trim()
748 }
749
750 #[inline]
761 pub fn trim_start(&self) -> &str {
762 self.as_str().trim_start()
763 }
764
765 #[inline]
776 pub fn trim_end(&self) -> &str {
777 self.as_str().trim_end()
778 }
779
780 #[inline]
794 pub fn split<'a, P>(&'a self, pat: P) -> SplitWrapper<'a>
795 where
796 P: SplitPattern<'a>,
797 {
798 pat.split_str(self.as_str())
799 }
800
801 #[inline]
813 pub fn lines(&self) -> impl Iterator<Item = &str> {
814 self.as_str().lines()
815 }
816
817 #[inline]
831 pub fn chars(&self) -> str::Chars<'_> {
832 self.as_str().chars()
833 }
834
835 #[inline]
848 pub fn to_uppercase(&self) -> CheetahString {
849 CheetahString::from_string(self.as_str().to_uppercase())
850 }
851
852 #[inline]
863 pub fn to_lowercase(&self) -> CheetahString {
864 CheetahString::from_string(self.as_str().to_lowercase())
865 }
866
867 #[inline]
878 pub fn replace<P: AsRef<str>>(&self, from: P, to: &str) -> CheetahString {
879 CheetahString::from_string(self.as_str().replace(from.as_ref(), to))
880 }
881
882 #[inline]
893 pub fn replacen<P: AsRef<str>>(&self, from: P, to: &str, count: usize) -> CheetahString {
894 CheetahString::from_string(self.as_str().replacen(from.as_ref(), to, count))
895 }
896
897 #[inline]
913 pub fn substring(&self, start: usize, end: usize) -> CheetahString {
914 CheetahString::from_slice(&self.as_str()[start..end])
915 }
916
917 #[inline]
928 pub fn repeat(&self, n: usize) -> CheetahString {
929 CheetahString::from_string(self.as_str().repeat(n))
930 }
931
932 #[inline]
950 pub fn with_capacity(capacity: usize) -> Self {
951 if capacity <= INLINE_CAPACITY {
952 CheetahString::empty()
953 } else {
954 CheetahString::from_builder_string(String::with_capacity(capacity))
955 }
956 }
957
958 #[inline]
959 fn push_str_internal(&mut self, string: &str) {
960 if string.is_empty() {
961 return;
962 }
963
964 match &mut self.inner {
965 InnerString::Inline { len, data } => {
966 let total_len = *len as usize + string.len();
967 if total_len <= INLINE_CAPACITY {
968 data[*len as usize..total_len].copy_from_slice(string.as_bytes());
969 *len = total_len as u8;
970 return;
971 }
972 }
973 InnerString::Owned(s) => {
974 s.push_str(string);
975 return;
976 }
977 _ => {}
978 }
979
980 let total_len = self.len() + string.len();
981 let mut result = String::with_capacity(total_len);
982 result.push_str(self.as_str());
983 result.push_str(string);
984 *self = CheetahString::from_builder_string(result);
985 }
986
987 #[inline]
1005 pub fn push_str(&mut self, string: &str) {
1006 self.push_str_internal(string);
1007 }
1008
1009 #[inline]
1024 pub fn reserve(&mut self, additional: usize) {
1025 if additional == 0 {
1026 return;
1027 }
1028
1029 match &mut self.inner {
1030 InnerString::Inline { len, .. } if *len as usize + additional <= INLINE_CAPACITY => {
1031 return;
1032 }
1033 InnerString::Inline { .. } => {}
1034 InnerString::Owned(s) => {
1035 s.reserve(additional);
1036 return;
1037 }
1038 _ => {}
1039 }
1040
1041 let new_len = self.len() + additional;
1042 let mut s = String::with_capacity(new_len);
1043 s.push_str(self.as_str());
1044 *self = CheetahString::from_builder_string(s);
1045 }
1046}
1047
1048impl PartialEq for CheetahString {
1049 #[inline]
1050 fn eq(&self, other: &Self) -> bool {
1051 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1052 {
1053 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1054 }
1055 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1056 {
1057 self.as_str() == other.as_str()
1058 }
1059 }
1060}
1061
1062impl PartialEq<str> for CheetahString {
1063 #[inline]
1064 fn eq(&self, other: &str) -> bool {
1065 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1066 {
1067 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1068 }
1069 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1070 {
1071 self.as_str() == other
1072 }
1073 }
1074}
1075
1076impl PartialEq<String> for CheetahString {
1077 #[inline]
1078 fn eq(&self, other: &String) -> bool {
1079 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1080 {
1081 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1082 }
1083 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1084 {
1085 self.as_str() == other.as_str()
1086 }
1087 }
1088}
1089
1090impl PartialEq<Vec<u8>> for CheetahString {
1091 #[inline]
1092 fn eq(&self, other: &Vec<u8>) -> bool {
1093 self.as_bytes() == other.as_slice()
1094 }
1095}
1096
1097impl<'a> PartialEq<&'a str> for CheetahString {
1098 #[inline]
1099 fn eq(&self, other: &&'a str) -> bool {
1100 self.as_str() == *other
1101 }
1102}
1103
1104impl PartialEq<CheetahString> for str {
1105 #[inline]
1106 fn eq(&self, other: &CheetahString) -> bool {
1107 self == other.as_str()
1108 }
1109}
1110
1111impl PartialEq<CheetahString> for String {
1112 #[inline]
1113 fn eq(&self, other: &CheetahString) -> bool {
1114 self.as_str() == other.as_str()
1115 }
1116}
1117
1118impl PartialEq<CheetahString> for &str {
1119 #[inline]
1120 fn eq(&self, other: &CheetahString) -> bool {
1121 *self == other.as_str()
1122 }
1123}
1124
1125impl Eq for CheetahString {}
1126
1127impl PartialOrd for CheetahString {
1128 #[inline]
1129 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1130 Some(self.cmp(other))
1131 }
1132}
1133
1134impl Ord for CheetahString {
1135 #[inline]
1136 fn cmp(&self, other: &Self) -> Ordering {
1137 self.as_str().cmp(other.as_str())
1138 }
1139}
1140
1141impl Hash for CheetahString {
1142 #[inline]
1143 fn hash<H: Hasher>(&self, state: &mut H) {
1144 self.as_str().hash(state);
1145 }
1146}
1147
1148impl Display for CheetahString {
1149 #[inline]
1150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1151 self.as_str().fmt(f)
1152 }
1153}
1154
1155impl fmt::Debug for CheetahString {
1156 #[inline]
1157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1158 fmt::Debug::fmt(self.as_str(), f)
1159 }
1160}
1161
1162impl Borrow<str> for CheetahString {
1163 #[inline]
1164 fn borrow(&self) -> &str {
1165 self.as_str()
1166 }
1167}
1168
1169impl Add<&str> for CheetahString {
1172 type Output = CheetahString;
1173
1174 #[inline]
1186 fn add(mut self, rhs: &str) -> Self::Output {
1187 self.push_str_internal(rhs);
1188 self
1189 }
1190}
1191
1192impl Add<&CheetahString> for CheetahString {
1193 type Output = CheetahString;
1194
1195 #[inline]
1208 fn add(mut self, rhs: &CheetahString) -> Self::Output {
1209 self.push_str_internal(rhs.as_str());
1210 self
1211 }
1212}
1213
1214impl Add<String> for CheetahString {
1215 type Output = CheetahString;
1216
1217 #[inline]
1229 fn add(mut self, rhs: String) -> Self::Output {
1230 if self.is_empty() {
1231 return CheetahString::from_string_owned(rhs);
1232 }
1233
1234 self.push_str_internal(&rhs);
1235 self
1236 }
1237}
1238
1239impl AddAssign<&str> for CheetahString {
1240 #[inline]
1252 fn add_assign(&mut self, rhs: &str) {
1253 self.push_str_internal(rhs);
1254 }
1255}
1256
1257impl AddAssign<&CheetahString> for CheetahString {
1258 #[inline]
1271 fn add_assign(&mut self, rhs: &CheetahString) {
1272 self.push_str_internal(rhs.as_str());
1273 }
1274}
1275
1276const INLINE_CAPACITY: usize = 23;
1278
1279#[derive(Clone)]
1290pub(super) enum InnerString {
1291 Inline {
1294 len: u8,
1295 data: [u8; INLINE_CAPACITY],
1296 },
1297 Static(&'static str),
1299 Shared(Arc<str>),
1302 Owned(String),
1304}
1305
1306mod private {
1308 use alloc::string::String;
1309
1310 pub trait Sealed {}
1311 impl Sealed for char {}
1312 impl Sealed for &str {}
1313 impl Sealed for &String {}
1314
1315 pub trait SplitSealed {}
1316 impl SplitSealed for char {}
1317 impl SplitSealed for &str {}
1318}
1319
1320pub trait StrPattern: private::Sealed {
1322 #[doc(hidden)]
1323 fn as_str_pattern(&self) -> StrPatternImpl<'_>;
1324}
1325
1326#[doc(hidden)]
1327pub enum StrPatternImpl<'a> {
1328 Char(char),
1329 Str(&'a str),
1330}
1331
1332impl StrPattern for char {
1333 #[inline]
1334 fn as_str_pattern(&self) -> StrPatternImpl<'_> {
1335 StrPatternImpl::Char(*self)
1336 }
1337}
1338
1339impl StrPattern for &str {
1340 #[inline]
1341 fn as_str_pattern(&self) -> StrPatternImpl<'_> {
1342 StrPatternImpl::Str(self)
1343 }
1344}
1345
1346impl StrPattern for &String {
1347 #[inline]
1348 fn as_str_pattern(&self) -> StrPatternImpl<'_> {
1349 StrPatternImpl::Str(self.as_str())
1350 }
1351}
1352
1353pub trait SplitPattern<'a>: private::SplitSealed {
1355 #[doc(hidden)]
1356 fn split_str(self, s: &'a str) -> SplitWrapper<'a>;
1357}
1358
1359impl SplitPattern<'_> for char {
1360 fn split_str(self, s: &str) -> SplitWrapper<'_> {
1361 SplitWrapper::Char(s.split(self))
1362 }
1363}
1364
1365impl<'a> SplitPattern<'a> for &'a str {
1366 fn split_str(self, s: &'a str) -> SplitWrapper<'a> {
1367 let inner = match single_char_pattern(self) {
1368 Some(ch) => SplitStrInner::Char(s.split(ch)),
1369 None => SplitStrInner::Str(s.split(self)),
1370 };
1371
1372 SplitWrapper::Str(SplitStr(inner))
1373 }
1374}
1375
1376pub struct SplitStr<'a>(SplitStrInner<'a>);
1378
1379enum SplitStrInner<'a> {
1380 Str(str::Split<'a, &'a str>),
1381 Char(str::Split<'a, char>),
1382}
1383
1384#[inline]
1385fn single_char_pattern(pattern: &str) -> Option<char> {
1386 let mut chars = pattern.chars();
1387 let ch = chars.next()?;
1388
1389 if chars.next().is_none() {
1390 Some(ch)
1391 } else {
1392 None
1393 }
1394}
1395
1396impl<'a> Iterator for SplitStr<'a> {
1397 type Item = &'a str;
1398
1399 fn next(&mut self) -> Option<Self::Item> {
1400 match &mut self.0 {
1401 SplitStrInner::Str(iter) => iter.next(),
1402 SplitStrInner::Char(iter) => iter.next(),
1403 }
1404 }
1405}
1406
1407pub enum SplitWrapper<'a> {
1409 #[doc(hidden)]
1410 Char(str::Split<'a, char>),
1411 #[doc(hidden)]
1412 Str(SplitStr<'a>),
1413}
1414
1415impl<'a> Iterator for SplitWrapper<'a> {
1416 type Item = &'a str;
1417
1418 fn next(&mut self) -> Option<Self::Item> {
1419 match self {
1420 SplitWrapper::Char(iter) => iter.next(),
1421 SplitWrapper::Str(iter) => iter.next(),
1422 }
1423 }
1424}
1425
1426impl<'a> DoubleEndedIterator for SplitWrapper<'a> {
1427 fn next_back(&mut self) -> Option<Self::Item> {
1428 match self {
1429 SplitWrapper::Char(iter) => iter.next_back(),
1430 SplitWrapper::Str(_) => {
1431 panic!("split with string pattern does not support reverse iteration")
1434 }
1435 }
1436 }
1437}
1438
1439#[cfg(test)]
1440mod tests {
1441 use super::*;
1442 use alloc::{format, vec};
1443
1444 #[test]
1445 fn with_capacity_above_inline_uses_heap_storage() {
1446 let s = CheetahString::with_capacity(INLINE_CAPACITY + 8);
1447
1448 match &s.inner {
1449 InnerString::Owned(inner) => {
1450 assert!(inner.capacity() >= INLINE_CAPACITY + 8);
1451 }
1452 other => panic!(
1453 "expected heap-backed storage from with_capacity, got {:?}",
1454 core::mem::discriminant(other)
1455 ),
1456 }
1457 }
1458
1459 #[test]
1460 fn push_str_promotes_builder_growth_to_owned_storage() {
1461 let suffix = "a".repeat(INLINE_CAPACITY);
1462 let expected = format!("hello{suffix}");
1463 let mut s = CheetahString::from("hello");
1464
1465 s.push_str(&suffix);
1466
1467 match &s.inner {
1468 InnerString::Owned(inner) => {
1469 assert_eq!(inner.as_str(), expected.as_str());
1470 assert!(inner.capacity() >= expected.len());
1471 }
1472 other => panic!(
1473 "expected owned heap storage after builder growth, got {:?}",
1474 core::mem::discriminant(other)
1475 ),
1476 }
1477 }
1478
1479 #[test]
1480 fn long_borrowed_str_uses_shared_storage() {
1481 let value = "a".repeat(INLINE_CAPACITY + 1);
1482 let s = CheetahString::from_slice(&value);
1483
1484 match &s.inner {
1485 InnerString::Shared(inner) => assert_eq!(inner.as_ref(), value.as_str()),
1486 other => panic!(
1487 "expected Shared for long borrowed input, got {:?}",
1488 core::mem::discriminant(other)
1489 ),
1490 }
1491 }
1492
1493 #[test]
1494 fn try_from_vec_short_input_uses_inline_storage() {
1495 let s = CheetahString::try_from_vec(b"hello".to_vec()).expect("valid utf-8");
1496
1497 match &s.inner {
1498 InnerString::Inline { len, data } => {
1499 assert_eq!(*len as usize, 5);
1500 assert_eq!(&data[..5], b"hello");
1501 }
1502 other => panic!(
1503 "expected inline storage for short validated Vec<u8>, got {:?}",
1504 core::mem::discriminant(other)
1505 ),
1506 }
1507 }
1508
1509 #[test]
1510 fn long_vec_conversion_uses_owned_storage() {
1511 let value = "a".repeat(INLINE_CAPACITY + 1).into_bytes();
1512 let s = CheetahString::try_from_vec(value).expect("valid utf-8");
1513
1514 match &s.inner {
1515 InnerString::Owned(inner) => {
1516 assert_eq!(inner.len(), INLINE_CAPACITY + 1);
1517 assert_eq!(inner.as_bytes(), vec![b'a'; INLINE_CAPACITY + 1].as_slice());
1518 }
1519 other => panic!(
1520 "expected Owned for long Vec<u8> conversion, got {:?}",
1521 core::mem::discriminant(other)
1522 ),
1523 }
1524 }
1525}