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 #[inline]
254 pub unsafe fn from_utf8_unchecked_vec(s: Vec<u8>) -> Self {
255 CheetahString::from_validated_vec_unchecked(s)
256 }
257
258 #[inline]
259 fn from_validated_vec_unchecked(s: Vec<u8>) -> Self {
260 if s.len() <= INLINE_CAPACITY {
261 let mut data = [0u8; INLINE_CAPACITY];
262 data[..s.len()].copy_from_slice(&s);
263 CheetahString {
264 inner: InnerString::Inline {
265 len: s.len() as u8,
266 data,
267 },
268 }
269 } else {
270 CheetahString::from_builder_string(unsafe { String::from_utf8_unchecked(s) })
272 }
273 }
274
275 pub fn try_from_vec(v: Vec<u8>) -> Result<Self, Utf8Error> {
294 str::from_utf8(&v)?;
295 Ok(CheetahString::from_validated_vec_unchecked(v))
296 }
297
298 pub fn try_from_bytes(b: &[u8]) -> Result<Self, Utf8Error> {
317 let s = str::from_utf8(b)?;
318 Ok(CheetahString::from_slice(s))
319 }
320
321 #[inline]
327 pub unsafe fn from_utf8_unchecked_bytes(b: &[u8]) -> Self {
328 CheetahString::from_slice(unsafe { str::from_utf8_unchecked(b) })
330 }
331
332 #[inline]
338 pub fn try_from_arc_vec(s: Arc<Vec<u8>>) -> Result<Self, Utf8Error> {
339 match Arc::try_unwrap(s) {
340 Ok(v) => CheetahString::try_from_vec(v),
341 Err(s) => {
342 let s = str::from_utf8(s.as_slice())?;
343 Ok(CheetahString::from_slice(s))
344 }
345 }
346 }
347
348 #[inline]
354 pub unsafe fn from_utf8_unchecked_arc_vec(s: Arc<Vec<u8>>) -> Self {
355 CheetahString::from_validated_arc_vec_unchecked(s)
356 }
357
358 #[inline]
359 fn from_validated_arc_vec_unchecked(s: Arc<Vec<u8>>) -> Self {
360 match Arc::try_unwrap(s) {
361 Ok(v) => CheetahString::from_validated_vec_unchecked(v),
362 Err(s) => {
363 unsafe { CheetahString::from_utf8_unchecked_bytes(s.as_slice()) }
365 }
366 }
367 }
368
369 #[inline]
370 pub fn from_slice(s: &str) -> Self {
371 if s.len() <= INLINE_CAPACITY {
372 let mut data = [0u8; INLINE_CAPACITY];
374 data[..s.len()].copy_from_slice(s.as_bytes());
375 CheetahString {
376 inner: InnerString::Inline {
377 len: s.len() as u8,
378 data,
379 },
380 }
381 } else {
382 let arc_str: Arc<str> = Arc::from(s);
384 CheetahString {
385 inner: InnerString::Shared(arc_str),
386 }
387 }
388 }
389
390 #[inline]
391 pub fn from_string(s: String) -> Self {
392 CheetahString::from_string_owned(s)
393 }
394
395 #[inline]
402 pub fn from_string_owned(s: String) -> Self {
403 CheetahString::from_builder_string(s)
404 }
405
406 #[inline]
412 pub fn from_string_shared(s: String) -> Self {
413 if s.len() <= INLINE_CAPACITY {
414 let mut data = [0u8; INLINE_CAPACITY];
416 data[..s.len()].copy_from_slice(s.as_bytes());
417 CheetahString {
418 inner: InnerString::Inline {
419 len: s.len() as u8,
420 data,
421 },
422 }
423 } else {
424 let arc_str: Arc<str> = s.into_boxed_str().into();
426 CheetahString {
427 inner: InnerString::Shared(arc_str),
428 }
429 }
430 }
431
432 #[inline]
433 fn from_builder_string(s: String) -> Self {
434 if s.len() <= INLINE_CAPACITY && s.capacity() <= INLINE_CAPACITY {
435 let mut data = [0u8; INLINE_CAPACITY];
436 data[..s.len()].copy_from_slice(s.as_bytes());
437 CheetahString {
438 inner: InnerString::Inline {
439 len: s.len() as u8,
440 data,
441 },
442 }
443 } else {
444 CheetahString {
445 inner: InnerString::Owned(s),
446 }
447 }
448 }
449
450 #[inline]
451 pub fn from_arc_string(s: Arc<String>) -> Self {
452 match Arc::try_unwrap(s) {
453 Ok(s) => CheetahString::from_builder_string(s),
454 Err(s) => CheetahString::from_slice(s.as_str()),
455 }
456 }
457
458 #[inline]
459 #[cfg(feature = "bytes")]
460 pub fn try_from_bytes_buf(b: bytes::Bytes) -> Result<Self, Utf8Error> {
461 str::from_utf8(b.as_ref())?;
462 Ok(CheetahString::from_validated_bytes_unchecked(b))
463 }
464
465 #[inline]
471 #[cfg(feature = "bytes")]
472 pub unsafe fn from_utf8_unchecked_bytes_buf(b: bytes::Bytes) -> Self {
473 CheetahString::from_validated_bytes_unchecked(b)
474 }
475
476 #[inline]
477 #[cfg(feature = "bytes")]
478 fn from_validated_bytes_unchecked(b: bytes::Bytes) -> Self {
479 unsafe { CheetahString::from_utf8_unchecked_bytes(b.as_ref()) }
481 }
482
483 #[inline]
484 pub fn as_str(&self) -> &str {
485 match &self.inner {
486 InnerString::Inline { len, data } => {
487 unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
490 }
491 InnerString::Static(s) => s,
492 InnerString::Shared(s) => s.as_ref(),
493 InnerString::Owned(s) => s.as_str(),
494 }
495 }
496
497 #[inline]
498 pub fn as_bytes(&self) -> &[u8] {
499 match &self.inner {
500 InnerString::Inline { len, data } => &data[..*len as usize],
501 InnerString::Static(s) => s.as_bytes(),
502 InnerString::Shared(s) => s.as_bytes(),
503 InnerString::Owned(s) => s.as_bytes(),
504 }
505 }
506
507 #[inline]
508 pub fn len(&self) -> usize {
509 match &self.inner {
510 InnerString::Inline { len, .. } => *len as usize,
511 InnerString::Static(s) => s.len(),
512 InnerString::Shared(s) => s.len(),
513 InnerString::Owned(s) => s.len(),
514 }
515 }
516
517 #[inline]
518 pub fn is_empty(&self) -> bool {
519 match &self.inner {
520 InnerString::Inline { len, .. } => *len == 0,
521 InnerString::Static(s) => s.is_empty(),
522 InnerString::Shared(s) => s.is_empty(),
523 InnerString::Owned(s) => s.is_empty(),
524 }
525 }
526
527 #[inline]
545 pub fn starts_with<P: StrPattern>(&self, pat: P) -> bool {
546 match pat.as_str_pattern() {
547 StrPatternImpl::Char(c) => self.as_str().starts_with(c),
548 StrPatternImpl::Str(s) => {
549 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
550 {
551 if s.len() >= crate::simd::SIMD_THRESHOLD {
552 return crate::simd::starts_with_bytes(self.as_bytes(), s.as_bytes());
553 }
554 }
555
556 self.as_str().starts_with(s)
557 }
558 }
559 }
560
561 #[inline]
573 pub fn starts_with_char(&self, pat: char) -> bool {
574 self.as_str().starts_with(pat)
575 }
576
577 #[inline]
593 pub fn ends_with<P: StrPattern>(&self, pat: P) -> bool {
594 match pat.as_str_pattern() {
595 StrPatternImpl::Char(c) => self.as_str().ends_with(c),
596 StrPatternImpl::Str(s) => {
597 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
598 {
599 if s.len() >= crate::simd::SIMD_THRESHOLD {
600 return crate::simd::ends_with_bytes(self.as_bytes(), s.as_bytes());
601 }
602 }
603
604 self.as_str().ends_with(s)
605 }
606 }
607 }
608
609 #[inline]
621 pub fn ends_with_char(&self, pat: char) -> bool {
622 self.as_str().ends_with(pat)
623 }
624
625 #[inline]
641 pub fn contains<P: StrPattern>(&self, pat: P) -> bool {
642 match pat.as_str_pattern() {
643 StrPatternImpl::Char(c) => self.as_str().contains(c),
644 StrPatternImpl::Str(s) => {
645 crate::search::find_bytes(self.as_bytes(), s.as_bytes()).is_some()
646 }
647 }
648 }
649
650 #[inline]
662 pub fn contains_char(&self, pat: char) -> bool {
663 self.as_str().contains(pat)
664 }
665
666 #[inline]
681 pub fn find<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
682 let pat = pat.as_ref();
683 crate::search::find_bytes(self.as_bytes(), pat.as_bytes())
684 }
685
686 #[inline]
697 pub fn rfind<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
698 crate::search::rfind_bytes(self.as_bytes(), pat.as_ref().as_bytes())
699 }
700
701 #[inline]
712 pub fn trim(&self) -> &str {
713 self.as_str().trim()
714 }
715
716 #[inline]
727 pub fn trim_start(&self) -> &str {
728 self.as_str().trim_start()
729 }
730
731 #[inline]
742 pub fn trim_end(&self) -> &str {
743 self.as_str().trim_end()
744 }
745
746 #[inline]
760 pub fn split<'a, P>(&'a self, pat: P) -> SplitWrapper<'a>
761 where
762 P: SplitPattern<'a>,
763 {
764 pat.split_str(self.as_str())
765 }
766
767 #[inline]
779 pub fn lines(&self) -> impl Iterator<Item = &str> {
780 self.as_str().lines()
781 }
782
783 #[inline]
797 pub fn chars(&self) -> str::Chars<'_> {
798 self.as_str().chars()
799 }
800
801 #[inline]
814 pub fn to_uppercase(&self) -> CheetahString {
815 CheetahString::from_string(self.as_str().to_uppercase())
816 }
817
818 #[inline]
829 pub fn to_lowercase(&self) -> CheetahString {
830 CheetahString::from_string(self.as_str().to_lowercase())
831 }
832
833 #[inline]
844 pub fn replace<P: AsRef<str>>(&self, from: P, to: &str) -> CheetahString {
845 CheetahString::from_string(self.as_str().replace(from.as_ref(), to))
846 }
847
848 #[inline]
859 pub fn replacen<P: AsRef<str>>(&self, from: P, to: &str, count: usize) -> CheetahString {
860 CheetahString::from_string(self.as_str().replacen(from.as_ref(), to, count))
861 }
862
863 #[inline]
879 pub fn substring(&self, start: usize, end: usize) -> CheetahString {
880 CheetahString::from_slice(&self.as_str()[start..end])
881 }
882
883 #[inline]
894 pub fn repeat(&self, n: usize) -> CheetahString {
895 CheetahString::from_string(self.as_str().repeat(n))
896 }
897
898 #[inline]
916 pub fn with_capacity(capacity: usize) -> Self {
917 if capacity <= INLINE_CAPACITY {
918 CheetahString::empty()
919 } else {
920 CheetahString::from_builder_string(String::with_capacity(capacity))
921 }
922 }
923
924 #[inline]
925 fn push_str_internal(&mut self, string: &str) {
926 if string.is_empty() {
927 return;
928 }
929
930 match &mut self.inner {
931 InnerString::Inline { len, data } => {
932 let total_len = *len as usize + string.len();
933 if total_len <= INLINE_CAPACITY {
934 data[*len as usize..total_len].copy_from_slice(string.as_bytes());
935 *len = total_len as u8;
936 return;
937 }
938 }
939 InnerString::Owned(s) => {
940 s.push_str(string);
941 return;
942 }
943 _ => {}
944 }
945
946 let total_len = self.len() + string.len();
947 let mut result = String::with_capacity(total_len);
948 result.push_str(self.as_str());
949 result.push_str(string);
950 *self = CheetahString::from_builder_string(result);
951 }
952
953 #[inline]
971 pub fn push_str(&mut self, string: &str) {
972 self.push_str_internal(string);
973 }
974
975 #[inline]
990 pub fn reserve(&mut self, additional: usize) {
991 if additional == 0 {
992 return;
993 }
994
995 match &mut self.inner {
996 InnerString::Inline { len, .. } if *len as usize + additional <= INLINE_CAPACITY => {
997 return;
998 }
999 InnerString::Inline { .. } => {}
1000 InnerString::Owned(s) => {
1001 s.reserve(additional);
1002 return;
1003 }
1004 _ => {}
1005 }
1006
1007 let new_len = self.len() + additional;
1008 let mut s = String::with_capacity(new_len);
1009 s.push_str(self.as_str());
1010 *self = CheetahString::from_builder_string(s);
1011 }
1012}
1013
1014impl PartialEq for CheetahString {
1015 #[inline]
1016 fn eq(&self, other: &Self) -> bool {
1017 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1018 {
1019 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1020 }
1021 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1022 {
1023 self.as_str() == other.as_str()
1024 }
1025 }
1026}
1027
1028impl PartialEq<str> for CheetahString {
1029 #[inline]
1030 fn eq(&self, other: &str) -> bool {
1031 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1032 {
1033 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1034 }
1035 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1036 {
1037 self.as_str() == other
1038 }
1039 }
1040}
1041
1042impl PartialEq<String> for CheetahString {
1043 #[inline]
1044 fn eq(&self, other: &String) -> bool {
1045 #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1046 {
1047 crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1048 }
1049 #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1050 {
1051 self.as_str() == other.as_str()
1052 }
1053 }
1054}
1055
1056impl PartialEq<Vec<u8>> for CheetahString {
1057 #[inline]
1058 fn eq(&self, other: &Vec<u8>) -> bool {
1059 self.as_bytes() == other.as_slice()
1060 }
1061}
1062
1063impl<'a> PartialEq<&'a str> for CheetahString {
1064 #[inline]
1065 fn eq(&self, other: &&'a str) -> bool {
1066 self.as_str() == *other
1067 }
1068}
1069
1070impl PartialEq<CheetahString> for str {
1071 #[inline]
1072 fn eq(&self, other: &CheetahString) -> bool {
1073 self == other.as_str()
1074 }
1075}
1076
1077impl PartialEq<CheetahString> for String {
1078 #[inline]
1079 fn eq(&self, other: &CheetahString) -> bool {
1080 self.as_str() == other.as_str()
1081 }
1082}
1083
1084impl PartialEq<CheetahString> for &str {
1085 #[inline]
1086 fn eq(&self, other: &CheetahString) -> bool {
1087 *self == other.as_str()
1088 }
1089}
1090
1091impl Eq for CheetahString {}
1092
1093impl PartialOrd for CheetahString {
1094 #[inline]
1095 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1096 Some(self.cmp(other))
1097 }
1098}
1099
1100impl Ord for CheetahString {
1101 #[inline]
1102 fn cmp(&self, other: &Self) -> Ordering {
1103 self.as_str().cmp(other.as_str())
1104 }
1105}
1106
1107impl Hash for CheetahString {
1108 #[inline]
1109 fn hash<H: Hasher>(&self, state: &mut H) {
1110 self.as_str().hash(state);
1111 }
1112}
1113
1114impl Display for CheetahString {
1115 #[inline]
1116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1117 self.as_str().fmt(f)
1118 }
1119}
1120
1121impl fmt::Debug for CheetahString {
1122 #[inline]
1123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1124 fmt::Debug::fmt(self.as_str(), f)
1125 }
1126}
1127
1128impl Borrow<str> for CheetahString {
1129 #[inline]
1130 fn borrow(&self) -> &str {
1131 self.as_str()
1132 }
1133}
1134
1135impl Add<&str> for CheetahString {
1138 type Output = CheetahString;
1139
1140 #[inline]
1152 fn add(mut self, rhs: &str) -> Self::Output {
1153 self.push_str_internal(rhs);
1154 self
1155 }
1156}
1157
1158impl Add<&CheetahString> for CheetahString {
1159 type Output = CheetahString;
1160
1161 #[inline]
1174 fn add(mut self, rhs: &CheetahString) -> Self::Output {
1175 self.push_str_internal(rhs.as_str());
1176 self
1177 }
1178}
1179
1180impl Add<String> for CheetahString {
1181 type Output = CheetahString;
1182
1183 #[inline]
1195 fn add(mut self, rhs: String) -> Self::Output {
1196 if self.is_empty() {
1197 return CheetahString::from_string_owned(rhs);
1198 }
1199
1200 self.push_str_internal(&rhs);
1201 self
1202 }
1203}
1204
1205impl AddAssign<&str> for CheetahString {
1206 #[inline]
1218 fn add_assign(&mut self, rhs: &str) {
1219 self.push_str_internal(rhs);
1220 }
1221}
1222
1223impl AddAssign<&CheetahString> for CheetahString {
1224 #[inline]
1237 fn add_assign(&mut self, rhs: &CheetahString) {
1238 self.push_str_internal(rhs.as_str());
1239 }
1240}
1241
1242const INLINE_CAPACITY: usize = 23;
1244
1245#[derive(Clone)]
1256pub(super) enum InnerString {
1257 Inline {
1260 len: u8,
1261 data: [u8; INLINE_CAPACITY],
1262 },
1263 Static(&'static str),
1265 Shared(Arc<str>),
1268 Owned(String),
1270}
1271
1272mod private {
1274 use alloc::string::String;
1275
1276 pub trait Sealed {}
1277 impl Sealed for char {}
1278 impl Sealed for &str {}
1279 impl Sealed for &String {}
1280
1281 pub trait SplitSealed {}
1282 impl SplitSealed for char {}
1283 impl SplitSealed for &str {}
1284}
1285
1286pub trait StrPattern: private::Sealed {
1288 #[doc(hidden)]
1289 fn as_str_pattern(&self) -> StrPatternImpl<'_>;
1290}
1291
1292#[doc(hidden)]
1293pub enum StrPatternImpl<'a> {
1294 Char(char),
1295 Str(&'a str),
1296}
1297
1298impl StrPattern for char {
1299 #[inline]
1300 fn as_str_pattern(&self) -> StrPatternImpl<'_> {
1301 StrPatternImpl::Char(*self)
1302 }
1303}
1304
1305impl StrPattern for &str {
1306 #[inline]
1307 fn as_str_pattern(&self) -> StrPatternImpl<'_> {
1308 StrPatternImpl::Str(self)
1309 }
1310}
1311
1312impl StrPattern for &String {
1313 #[inline]
1314 fn as_str_pattern(&self) -> StrPatternImpl<'_> {
1315 StrPatternImpl::Str(self.as_str())
1316 }
1317}
1318
1319pub trait SplitPattern<'a>: private::SplitSealed {
1321 #[doc(hidden)]
1322 fn split_str(self, s: &'a str) -> SplitWrapper<'a>;
1323}
1324
1325impl SplitPattern<'_> for char {
1326 fn split_str(self, s: &str) -> SplitWrapper<'_> {
1327 SplitWrapper::Char(s.split(self))
1328 }
1329}
1330
1331impl<'a> SplitPattern<'a> for &'a str {
1332 fn split_str(self, s: &'a str) -> SplitWrapper<'a> {
1333 let inner = match single_char_pattern(self) {
1334 Some(ch) => SplitStrInner::Char(s.split(ch)),
1335 None => SplitStrInner::Str(s.split(self)),
1336 };
1337
1338 SplitWrapper::Str(SplitStr(inner))
1339 }
1340}
1341
1342pub struct SplitStr<'a>(SplitStrInner<'a>);
1344
1345enum SplitStrInner<'a> {
1346 Str(str::Split<'a, &'a str>),
1347 Char(str::Split<'a, char>),
1348}
1349
1350#[inline]
1351fn single_char_pattern(pattern: &str) -> Option<char> {
1352 let mut chars = pattern.chars();
1353 let ch = chars.next()?;
1354
1355 if chars.next().is_none() {
1356 Some(ch)
1357 } else {
1358 None
1359 }
1360}
1361
1362impl<'a> Iterator for SplitStr<'a> {
1363 type Item = &'a str;
1364
1365 fn next(&mut self) -> Option<Self::Item> {
1366 match &mut self.0 {
1367 SplitStrInner::Str(iter) => iter.next(),
1368 SplitStrInner::Char(iter) => iter.next(),
1369 }
1370 }
1371}
1372
1373pub enum SplitWrapper<'a> {
1375 #[doc(hidden)]
1376 Char(str::Split<'a, char>),
1377 #[doc(hidden)]
1378 Str(SplitStr<'a>),
1379}
1380
1381impl<'a> Iterator for SplitWrapper<'a> {
1382 type Item = &'a str;
1383
1384 fn next(&mut self) -> Option<Self::Item> {
1385 match self {
1386 SplitWrapper::Char(iter) => iter.next(),
1387 SplitWrapper::Str(iter) => iter.next(),
1388 }
1389 }
1390}
1391
1392impl<'a> DoubleEndedIterator for SplitWrapper<'a> {
1393 fn next_back(&mut self) -> Option<Self::Item> {
1394 match self {
1395 SplitWrapper::Char(iter) => iter.next_back(),
1396 SplitWrapper::Str(_) => {
1397 panic!("split with string pattern does not support reverse iteration")
1400 }
1401 }
1402 }
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407 use super::*;
1408 use alloc::{format, vec};
1409
1410 #[test]
1411 fn with_capacity_above_inline_uses_heap_storage() {
1412 let s = CheetahString::with_capacity(INLINE_CAPACITY + 8);
1413
1414 match &s.inner {
1415 InnerString::Owned(inner) => {
1416 assert!(inner.capacity() >= INLINE_CAPACITY + 8);
1417 }
1418 other => panic!(
1419 "expected heap-backed storage from with_capacity, got {:?}",
1420 core::mem::discriminant(other)
1421 ),
1422 }
1423 }
1424
1425 #[test]
1426 fn push_str_promotes_builder_growth_to_owned_storage() {
1427 let suffix = "a".repeat(INLINE_CAPACITY);
1428 let expected = format!("hello{suffix}");
1429 let mut s = CheetahString::from("hello");
1430
1431 s.push_str(&suffix);
1432
1433 match &s.inner {
1434 InnerString::Owned(inner) => {
1435 assert_eq!(inner.as_str(), expected.as_str());
1436 assert!(inner.capacity() >= expected.len());
1437 }
1438 other => panic!(
1439 "expected owned heap storage after builder growth, got {:?}",
1440 core::mem::discriminant(other)
1441 ),
1442 }
1443 }
1444
1445 #[test]
1446 fn long_borrowed_str_uses_shared_storage() {
1447 let value = "a".repeat(INLINE_CAPACITY + 1);
1448 let s = CheetahString::from_slice(&value);
1449
1450 match &s.inner {
1451 InnerString::Shared(inner) => assert_eq!(inner.as_ref(), value.as_str()),
1452 other => panic!(
1453 "expected Shared for long borrowed input, got {:?}",
1454 core::mem::discriminant(other)
1455 ),
1456 }
1457 }
1458
1459 #[test]
1460 fn try_from_vec_short_input_uses_inline_storage() {
1461 let s = CheetahString::try_from_vec(b"hello".to_vec()).expect("valid utf-8");
1462
1463 match &s.inner {
1464 InnerString::Inline { len, data } => {
1465 assert_eq!(*len as usize, 5);
1466 assert_eq!(&data[..5], b"hello");
1467 }
1468 other => panic!(
1469 "expected inline storage for short validated Vec<u8>, got {:?}",
1470 core::mem::discriminant(other)
1471 ),
1472 }
1473 }
1474
1475 #[test]
1476 fn long_vec_conversion_uses_owned_storage() {
1477 let value = "a".repeat(INLINE_CAPACITY + 1).into_bytes();
1478 let s = CheetahString::try_from_vec(value).expect("valid utf-8");
1479
1480 match &s.inner {
1481 InnerString::Owned(inner) => {
1482 assert_eq!(inner.len(), INLINE_CAPACITY + 1);
1483 assert_eq!(inner.as_bytes(), vec![b'a'; INLINE_CAPACITY + 1].as_slice());
1484 }
1485 other => panic!(
1486 "expected Owned for long Vec<u8> conversion, got {:?}",
1487 core::mem::discriminant(other)
1488 ),
1489 }
1490 }
1491}