Skip to main content

cheetah_string/
cheetah_string.rs

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    /// Allocates an owned [`CheetahString`] from a single character.
98    ///
99    /// # Example
100    /// ```rust
101    /// use cheetah_string::CheetahString;
102    /// let c: char = 'a';
103    /// let s: CheetahString = CheetahString::from(c);
104    /// assert_eq!("a", &s[..]);
105    /// ```
106    #[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                // SAFETY: Inline strings are always valid UTF-8
172                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    /// Creates a `CheetahString` from a byte vector without validating UTF-8.
248    ///
249    /// # Safety
250    ///
251    /// The caller must guarantee that `s` contains valid UTF-8 for the entire
252    /// lifetime of the returned `CheetahString`.
253    #[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            // SAFETY: Callers validate UTF-8 before reaching this helper.
271            CheetahString::from_builder_string(unsafe { String::from_utf8_unchecked(s) })
272        }
273    }
274
275    /// Creates a `CheetahString` from a byte vector with UTF-8 validation.
276    ///
277    /// # Errors
278    ///
279    /// Returns an error if the bytes are not valid UTF-8.
280    ///
281    /// # Examples
282    ///
283    /// ```
284    /// use cheetah_string::CheetahString;
285    ///
286    /// let bytes = vec![104, 101, 108, 108, 111]; // "hello"
287    /// let s = CheetahString::try_from_vec(bytes).unwrap();
288    /// assert_eq!(s, "hello");
289    ///
290    /// let invalid = vec![0xFF, 0xFE];
291    /// assert!(CheetahString::try_from_vec(invalid).is_err());
292    /// ```
293    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    /// Creates a `CheetahString` from a byte slice with UTF-8 validation.
299    ///
300    /// # Errors
301    ///
302    /// Returns an error if the bytes are not valid UTF-8.
303    ///
304    /// # Examples
305    ///
306    /// ```
307    /// use cheetah_string::CheetahString;
308    ///
309    /// let bytes = b"hello";
310    /// let s = CheetahString::try_from_bytes(bytes).unwrap();
311    /// assert_eq!(s, "hello");
312    ///
313    /// let invalid = &[0xFF, 0xFE];
314    /// assert!(CheetahString::try_from_bytes(invalid).is_err());
315    /// ```
316    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    /// Creates a `CheetahString` from a byte slice without validating UTF-8.
322    ///
323    /// # Safety
324    ///
325    /// The caller must guarantee that `b` contains valid UTF-8.
326    #[inline]
327    pub unsafe fn from_utf8_unchecked_bytes(b: &[u8]) -> Self {
328        // SAFETY: The caller guarantees that `b` contains valid UTF-8.
329        CheetahString::from_slice(unsafe { str::from_utf8_unchecked(b) })
330    }
331
332    /// Creates a `CheetahString` from a shared byte vector with UTF-8 validation.
333    ///
334    /// # Errors
335    ///
336    /// Returns an error if the bytes are not valid UTF-8.
337    #[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    /// Creates a `CheetahString` from a shared byte vector without validating UTF-8.
349    ///
350    /// # Safety
351    ///
352    /// The caller must guarantee that `s` contains valid UTF-8.
353    #[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                // SAFETY: Callers validate UTF-8 before reaching this helper.
364                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            // Use inline storage for short strings
373            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            // Use Arc<str> for long borrowed strings to avoid the extra String header.
383            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    /// Creates a `CheetahString` from an owned `String` while preserving
396    /// ownership and spare capacity for later mutation.
397    ///
398    /// This constructor is intended for builder-style paths that will continue
399    /// appending to the string. It keeps long strings in owned storage instead
400    /// of converting them to shared storage.
401    #[inline]
402    pub fn from_string_owned(s: String) -> Self {
403        CheetahString::from_builder_string(s)
404    }
405
406    /// Creates a `CheetahString` from an owned `String` using shared storage
407    /// for long immutable strings.
408    ///
409    /// New code that needs clone-cheap immutable strings should prefer
410    /// `CheetahStr`.
411    #[inline]
412    pub fn from_string_shared(s: String) -> Self {
413        if s.len() <= INLINE_CAPACITY {
414            // Use inline storage for short strings
415            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            // Use Arc<str> for long strings to avoid double allocation
425            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    /// Creates a `CheetahString` from `bytes::Bytes` without validating UTF-8.
466    ///
467    /// # Safety
468    ///
469    /// The caller must guarantee that `b` contains valid UTF-8.
470    #[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        // SAFETY: Callers validate UTF-8 before reaching this helper.
480        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                // SAFETY: Inline strings are only created from valid UTF-8 sources.
488                // The data is always valid UTF-8 up to len bytes.
489                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    // Query methods - delegate to &str
528
529    /// Returns `true` if the string starts with the given pattern.
530    ///
531    /// When the `simd` feature is enabled, this method uses SIMD instructions
532    /// for improved performance on longer patterns.
533    ///
534    /// # Examples
535    ///
536    /// ```
537    /// use cheetah_string::CheetahString;
538    ///
539    /// let s = CheetahString::from("hello world");
540    /// assert!(s.starts_with("hello"));
541    /// assert!(!s.starts_with("world"));
542    /// assert!(s.starts_with('h'));
543    /// ```
544    #[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    /// Returns `true` if the string starts with the given character.
562    ///
563    /// # Examples
564    ///
565    /// ```
566    /// use cheetah_string::CheetahString;
567    ///
568    /// let s = CheetahString::from("hello world");
569    /// assert!(s.starts_with_char('h'));
570    /// assert!(!s.starts_with_char('w'));
571    /// ```
572    #[inline]
573    pub fn starts_with_char(&self, pat: char) -> bool {
574        self.as_str().starts_with(pat)
575    }
576
577    /// Returns `true` if the string ends with the given pattern.
578    ///
579    /// When the `simd` feature is enabled, this method uses SIMD instructions
580    /// for improved performance on longer patterns.
581    ///
582    /// # Examples
583    ///
584    /// ```
585    /// use cheetah_string::CheetahString;
586    ///
587    /// let s = CheetahString::from("hello world");
588    /// assert!(s.ends_with("world"));
589    /// assert!(!s.ends_with("hello"));
590    /// assert!(s.ends_with('d'));
591    /// ```
592    #[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    /// Returns `true` if the string ends with the given character.
610    ///
611    /// # Examples
612    ///
613    /// ```
614    /// use cheetah_string::CheetahString;
615    ///
616    /// let s = CheetahString::from("hello world");
617    /// assert!(s.ends_with_char('d'));
618    /// assert!(!s.ends_with_char('h'));
619    /// ```
620    #[inline]
621    pub fn ends_with_char(&self, pat: char) -> bool {
622        self.as_str().ends_with(pat)
623    }
624
625    /// Returns `true` if the string contains the given pattern.
626    ///
627    /// When the `simd` feature is enabled, this method uses SIMD instructions
628    /// for improved performance on longer patterns.
629    ///
630    /// # Examples
631    ///
632    /// ```
633    /// use cheetah_string::CheetahString;
634    ///
635    /// let s = CheetahString::from("hello world");
636    /// assert!(s.contains("llo"));
637    /// assert!(!s.contains("xyz"));
638    /// assert!(s.contains('o'));
639    /// ```
640    #[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    /// Returns `true` if the string contains the given character.
651    ///
652    /// # Examples
653    ///
654    /// ```
655    /// use cheetah_string::CheetahString;
656    ///
657    /// let s = CheetahString::from("hello world");
658    /// assert!(s.contains_char('o'));
659    /// assert!(!s.contains_char('x'));
660    /// ```
661    #[inline]
662    pub fn contains_char(&self, pat: char) -> bool {
663        self.as_str().contains(pat)
664    }
665
666    /// Returns the byte index of the first occurrence of the pattern, or `None` if not found.
667    ///
668    /// When the `simd` feature is enabled, this method uses SIMD instructions
669    /// for improved performance on longer patterns.
670    ///
671    /// # Examples
672    ///
673    /// ```
674    /// use cheetah_string::CheetahString;
675    ///
676    /// let s = CheetahString::from("hello world");
677    /// assert_eq!(s.find("world"), Some(6));
678    /// assert_eq!(s.find("xyz"), None);
679    /// ```
680    #[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    /// Returns the byte index of the last occurrence of the pattern, or `None` if not found.
687    ///
688    /// # Examples
689    ///
690    /// ```
691    /// use cheetah_string::CheetahString;
692    ///
693    /// let s = CheetahString::from("hello hello");
694    /// assert_eq!(s.rfind("hello"), Some(6));
695    /// ```
696    #[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    /// Returns a string slice with leading and trailing whitespace removed.
702    ///
703    /// # Examples
704    ///
705    /// ```
706    /// use cheetah_string::CheetahString;
707    ///
708    /// let s = CheetahString::from("  hello  ");
709    /// assert_eq!(s.trim(), "hello");
710    /// ```
711    #[inline]
712    pub fn trim(&self) -> &str {
713        self.as_str().trim()
714    }
715
716    /// Returns a string slice with leading whitespace removed.
717    ///
718    /// # Examples
719    ///
720    /// ```
721    /// use cheetah_string::CheetahString;
722    ///
723    /// let s = CheetahString::from("  hello");
724    /// assert_eq!(s.trim_start(), "hello");
725    /// ```
726    #[inline]
727    pub fn trim_start(&self) -> &str {
728        self.as_str().trim_start()
729    }
730
731    /// Returns a string slice with trailing whitespace removed.
732    ///
733    /// # Examples
734    ///
735    /// ```
736    /// use cheetah_string::CheetahString;
737    ///
738    /// let s = CheetahString::from("hello  ");
739    /// assert_eq!(s.trim_end(), "hello");
740    /// ```
741    #[inline]
742    pub fn trim_end(&self) -> &str {
743        self.as_str().trim_end()
744    }
745
746    /// Splits the string by the given pattern.
747    ///
748    /// # Examples
749    ///
750    /// ```
751    /// use cheetah_string::CheetahString;
752    ///
753    /// let s = CheetahString::from("a,b,c");
754    /// let parts: Vec<&str> = s.split(",").collect();
755    /// assert_eq!(parts, vec!["a", "b", "c"]);
756    /// let parts2: Vec<&str> = s.split(',').collect();
757    /// assert_eq!(parts2, vec!["a", "b", "c"]);
758    /// ```
759    #[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    /// Returns an iterator over the lines of the string.
768    ///
769    /// # Examples
770    ///
771    /// ```
772    /// use cheetah_string::CheetahString;
773    ///
774    /// let s = CheetahString::from("line1\nline2\nline3");
775    /// let lines: Vec<&str> = s.lines().collect();
776    /// assert_eq!(lines, vec!["line1", "line2", "line3"]);
777    /// ```
778    #[inline]
779    pub fn lines(&self) -> impl Iterator<Item = &str> {
780        self.as_str().lines()
781    }
782
783    /// Returns an iterator over the characters of the string.
784    ///
785    /// # Examples
786    ///
787    /// ```
788    /// use cheetah_string::CheetahString;
789    ///
790    /// let s = CheetahString::from("hello");
791    /// let chars: Vec<char> = s.chars().collect();
792    /// assert_eq!(chars, vec!['h', 'e', 'l', 'l', 'o']);
793    /// let reversed: Vec<char> = s.chars().rev().collect();
794    /// assert_eq!(reversed, vec!['o', 'l', 'l', 'e', 'h']);
795    /// ```
796    #[inline]
797    pub fn chars(&self) -> str::Chars<'_> {
798        self.as_str().chars()
799    }
800
801    // Transformation methods - create new CheetahString
802
803    /// Returns a new `CheetahString` with all characters converted to uppercase.
804    ///
805    /// # Examples
806    ///
807    /// ```
808    /// use cheetah_string::CheetahString;
809    ///
810    /// let s = CheetahString::from("hello");
811    /// assert_eq!(s.to_uppercase(), "HELLO");
812    /// ```
813    #[inline]
814    pub fn to_uppercase(&self) -> CheetahString {
815        CheetahString::from_string(self.as_str().to_uppercase())
816    }
817
818    /// Returns a new `CheetahString` with all characters converted to lowercase.
819    ///
820    /// # Examples
821    ///
822    /// ```
823    /// use cheetah_string::CheetahString;
824    ///
825    /// let s = CheetahString::from("HELLO");
826    /// assert_eq!(s.to_lowercase(), "hello");
827    /// ```
828    #[inline]
829    pub fn to_lowercase(&self) -> CheetahString {
830        CheetahString::from_string(self.as_str().to_lowercase())
831    }
832
833    /// Replaces all occurrences of a pattern with another string.
834    ///
835    /// # Examples
836    ///
837    /// ```
838    /// use cheetah_string::CheetahString;
839    ///
840    /// let s = CheetahString::from("hello world");
841    /// assert_eq!(s.replace("world", "rust"), "hello rust");
842    /// ```
843    #[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    /// Returns a new `CheetahString` with the specified range replaced.
849    ///
850    /// # Examples
851    ///
852    /// ```
853    /// use cheetah_string::CheetahString;
854    ///
855    /// let s = CheetahString::from("hello world");
856    /// assert_eq!(s.replacen("l", "L", 1), "heLlo world");
857    /// ```
858    #[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    /// Returns a substring as a new `CheetahString`.
864    ///
865    /// # Panics
866    ///
867    /// Panics if the indices are not on valid UTF-8 character boundaries.
868    ///
869    /// # Examples
870    ///
871    /// ```
872    /// use cheetah_string::CheetahString;
873    ///
874    /// let s = CheetahString::from("hello world");
875    /// assert_eq!(s.substring(0, 5), "hello");
876    /// assert_eq!(s.substring(6, 11), "world");
877    /// ```
878    #[inline]
879    pub fn substring(&self, start: usize, end: usize) -> CheetahString {
880        CheetahString::from_slice(&self.as_str()[start..end])
881    }
882
883    /// Repeats the string `n` times.
884    ///
885    /// # Examples
886    ///
887    /// ```
888    /// use cheetah_string::CheetahString;
889    ///
890    /// let s = CheetahString::from("abc");
891    /// assert_eq!(s.repeat(3), "abcabcabc");
892    /// ```
893    #[inline]
894    pub fn repeat(&self, n: usize) -> CheetahString {
895        CheetahString::from_string(self.as_str().repeat(n))
896    }
897
898    // Incremental building methods
899
900    /// Creates a new `CheetahString` with the specified capacity.
901    ///
902    /// The string will be able to hold at least `capacity` bytes without reallocating.
903    /// If `capacity` is less than or equal to the inline capacity (23 bytes),
904    /// an empty inline string is returned.
905    ///
906    /// # Examples
907    ///
908    /// ```
909    /// use cheetah_string::CheetahString;
910    ///
911    /// let mut s = CheetahString::with_capacity(100);
912    /// s.push_str("hello");
913    /// assert_eq!(s, "hello");
914    /// ```
915    #[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    /// Appends a string slice to the end of this `CheetahString`.
954    ///
955    /// This method is optimized for incremental building and will:
956    /// - Mutate inline storage when possible
957    /// - Mutate owned heap storage in-place when capacity allows
958    /// - Only allocate when necessary
959    ///
960    /// # Examples
961    ///
962    /// ```
963    /// use cheetah_string::CheetahString;
964    ///
965    /// let mut s = CheetahString::from("Hello");
966    /// s.push_str(" ");
967    /// s.push_str("World");
968    /// assert_eq!(s, "Hello World");
969    /// ```
970    #[inline]
971    pub fn push_str(&mut self, string: &str) {
972        self.push_str_internal(string);
973    }
974
975    /// Reserves capacity for at least `additional` more bytes.
976    ///
977    /// This method will modify the internal representation if needed to ensure
978    /// that the string can hold at least `additional` more bytes without reallocating.
979    ///
980    /// # Examples
981    ///
982    /// ```
983    /// use cheetah_string::CheetahString;
984    ///
985    /// let mut s = CheetahString::from("hello");
986    /// s.reserve(100);
987    /// s.push_str(" world");
988    /// ```
989    #[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
1135// Add trait implementations for string concatenation
1136
1137impl Add<&str> for CheetahString {
1138    type Output = CheetahString;
1139
1140    /// Concatenates a `CheetahString` with a string slice.
1141    ///
1142    /// # Examples
1143    ///
1144    /// ```
1145    /// use cheetah_string::CheetahString;
1146    ///
1147    /// let s = CheetahString::from("Hello");
1148    /// let result = s + " World";
1149    /// assert_eq!(result, "Hello World");
1150    /// ```
1151    #[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    /// Concatenates two `CheetahString` values.
1162    ///
1163    /// # Examples
1164    ///
1165    /// ```
1166    /// use cheetah_string::CheetahString;
1167    ///
1168    /// let s1 = CheetahString::from("Hello");
1169    /// let s2 = CheetahString::from(" World");
1170    /// let result = s1 + &s2;
1171    /// assert_eq!(result, "Hello World");
1172    /// ```
1173    #[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    /// Concatenates a `CheetahString` with a `String`.
1184    ///
1185    /// # Examples
1186    ///
1187    /// ```
1188    /// use cheetah_string::CheetahString;
1189    ///
1190    /// let s = CheetahString::from("Hello");
1191    /// let result = s + String::from(" World");
1192    /// assert_eq!(result, "Hello World");
1193    /// ```
1194    #[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    /// Appends a string slice to a `CheetahString`.
1207    ///
1208    /// # Examples
1209    ///
1210    /// ```
1211    /// use cheetah_string::CheetahString;
1212    ///
1213    /// let mut s = CheetahString::from("Hello");
1214    /// s += " World";
1215    /// assert_eq!(s, "Hello World");
1216    /// ```
1217    #[inline]
1218    fn add_assign(&mut self, rhs: &str) {
1219        self.push_str_internal(rhs);
1220    }
1221}
1222
1223impl AddAssign<&CheetahString> for CheetahString {
1224    /// Appends a `CheetahString` to another `CheetahString`.
1225    ///
1226    /// # Examples
1227    ///
1228    /// ```
1229    /// use cheetah_string::CheetahString;
1230    ///
1231    /// let mut s1 = CheetahString::from("Hello");
1232    /// let s2 = CheetahString::from(" World");
1233    /// s1 += &s2;
1234    /// assert_eq!(s1, "Hello World");
1235    /// ```
1236    #[inline]
1237    fn add_assign(&mut self, rhs: &CheetahString) {
1238        self.push_str_internal(rhs.as_str());
1239    }
1240}
1241
1242/// Maximum capacity for inline string storage (23 bytes + 1 byte for length = 24 bytes total)
1243const INLINE_CAPACITY: usize = 23;
1244
1245/// The `InnerString` enum represents different types of string storage.
1246///
1247/// This enum uses Small String Optimization (SSO) to avoid heap allocations for short strings.
1248///
1249/// Variants:
1250///
1251/// * `Inline` - Inline storage for strings <= 23 bytes (zero heap allocations).
1252/// * `Static(&'static str)` - A static string slice (zero heap allocations).
1253/// * `Shared(Arc<str>)` - A reference-counted string slice (single heap allocation, optimized).
1254/// * `Owned(String)` - An owned heap string used for builder-style mutation.
1255#[derive(Clone)]
1256pub(super) enum InnerString {
1257    /// Inline storage for short strings (up to 23 bytes).
1258    /// Stores the length and data directly without heap allocation.
1259    Inline {
1260        len: u8,
1261        data: [u8; INLINE_CAPACITY],
1262    },
1263    /// Static string slice with 'static lifetime.
1264    Static(&'static str),
1265    /// Reference-counted string slice (single heap allocation).
1266    /// Preferred for long immutable strings created from owned or borrowed data.
1267    Shared(Arc<str>),
1268    /// Owned heap-allocated string used when exclusive mutability matters.
1269    Owned(String),
1270}
1271
1272// Sealed trait pattern to support both &str and char in starts_with/ends_with/contains
1273mod 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
1286/// A pattern that can be used with `starts_with` and `ends_with` methods.
1287pub 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
1319/// A pattern that can be used with `split` method.
1320pub 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
1342/// Helper struct for splitting strings by a string pattern
1343pub 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
1373/// Wrapper for split iterator that supports both char and str patterns
1374pub 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                // String pattern split doesn't support reverse iteration
1398                // This is consistent with std::str::Split<&str>
1399                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}