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
12mod pattern;
13mod repr;
14
15use crate::inline::InlineStr;
16use pattern::StrPatternImpl;
17pub use pattern::{SplitPattern, SplitStr, SplitWrapper, StrPattern};
18use repr::{InnerString, INLINE_CAPACITY};
19
20#[derive(Clone)]
21#[repr(transparent)]
22pub struct CheetahString {
23    inner: InnerString,
24}
25
26impl Default for CheetahString {
27    fn default() -> Self {
28        CheetahString {
29            inner: InnerString::Inline(InlineStr::empty()),
30        }
31    }
32}
33
34impl From<String> for CheetahString {
35    #[inline]
36    fn from(s: String) -> Self {
37        CheetahString::from_string(s)
38    }
39}
40
41impl From<Arc<String>> for CheetahString {
42    #[inline]
43    fn from(s: Arc<String>) -> Self {
44        CheetahString::from_arc_string(s)
45    }
46}
47
48impl<'a> From<&'a str> for CheetahString {
49    #[inline]
50    fn from(s: &'a str) -> Self {
51        CheetahString::from_slice(s)
52    }
53}
54
55impl<'a> TryFrom<&'a [u8]> for CheetahString {
56    type Error = Utf8Error;
57
58    #[inline]
59    fn try_from(b: &'a [u8]) -> Result<Self, Self::Error> {
60        CheetahString::try_from_bytes(b)
61    }
62}
63
64impl FromStr for CheetahString {
65    type Err = ParseError;
66    #[inline]
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        Ok(CheetahString::from_slice(s))
69    }
70}
71
72impl TryFrom<Vec<u8>> for CheetahString {
73    type Error = Utf8Error;
74
75    #[inline]
76    fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
77        CheetahString::try_from_vec(v)
78    }
79}
80
81impl From<Cow<'static, str>> for CheetahString {
82    #[inline]
83    fn from(cow: Cow<'static, str>) -> Self {
84        match cow {
85            Cow::Borrowed(s) => CheetahString::from_static_str(s),
86            Cow::Owned(s) => CheetahString::from_string(s),
87        }
88    }
89}
90
91impl From<Cow<'_, String>> for CheetahString {
92    #[inline]
93    fn from(cow: Cow<'_, String>) -> Self {
94        match cow {
95            Cow::Borrowed(s) => CheetahString::from_slice(s),
96            Cow::Owned(s) => CheetahString::from_string(s),
97        }
98    }
99}
100
101impl From<char> for CheetahString {
102    /// Allocates an owned [`CheetahString`] from a single character.
103    ///
104    /// # Example
105    /// ```rust
106    /// use cheetah_string::CheetahString;
107    /// let c: char = 'a';
108    /// let s: CheetahString = CheetahString::from(c);
109    /// assert_eq!("a", &s[..]);
110    /// ```
111    #[inline]
112    fn from(c: char) -> Self {
113        CheetahString::from_string(c.to_string())
114    }
115}
116
117impl<'a> FromIterator<&'a char> for CheetahString {
118    #[inline]
119    fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> CheetahString {
120        let mut buf = String::new();
121        buf.extend(iter);
122        CheetahString::from_string(buf)
123    }
124}
125
126impl<'a> FromIterator<&'a str> for CheetahString {
127    fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> CheetahString {
128        let mut buf = String::new();
129        buf.extend(iter);
130        CheetahString::from_string(buf)
131    }
132}
133
134impl FromIterator<String> for CheetahString {
135    #[inline]
136    fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
137        let mut buf = String::new();
138        buf.extend(iter);
139        CheetahString::from_string(buf)
140    }
141}
142
143impl<'a> FromIterator<&'a String> for CheetahString {
144    #[inline]
145    fn from_iter<T: IntoIterator<Item = &'a String>>(iter: T) -> Self {
146        let mut buf = String::new();
147        buf.extend(iter.into_iter().map(|s| s.as_str()));
148        CheetahString::from_string(buf)
149    }
150}
151
152#[cfg(feature = "bytes")]
153impl TryFrom<bytes::Bytes> for CheetahString {
154    type Error = Utf8Error;
155
156    #[inline]
157    fn try_from(b: bytes::Bytes) -> Result<Self, Self::Error> {
158        CheetahString::try_from_bytes_buf(b)
159    }
160}
161
162impl From<&CheetahString> for CheetahString {
163    #[inline]
164    fn from(s: &CheetahString) -> Self {
165        s.clone()
166    }
167}
168
169impl From<CheetahString> for String {
170    #[inline]
171    fn from(s: CheetahString) -> Self {
172        match s {
173            CheetahString {
174                inner: InnerString::Inline(inline),
175            } => inline.into_string(),
176            CheetahString {
177                inner: InnerString::Static(s),
178            } => s.to_string(),
179            CheetahString {
180                inner: InnerString::Shared(s),
181            } => s.to_string(),
182            CheetahString {
183                inner: InnerString::Owned(s),
184            } => s,
185        }
186    }
187}
188
189impl Deref for CheetahString {
190    type Target = str;
191
192    #[inline]
193    fn deref(&self) -> &Self::Target {
194        self.as_str()
195    }
196}
197
198impl AsRef<str> for CheetahString {
199    #[inline]
200    fn as_ref(&self) -> &str {
201        self.as_str()
202    }
203}
204
205impl AsRef<[u8]> for CheetahString {
206    #[inline]
207    fn as_ref(&self) -> &[u8] {
208        self.as_bytes()
209    }
210}
211
212impl AsRef<CheetahString> for CheetahString {
213    #[inline]
214    fn as_ref(&self) -> &CheetahString {
215        self
216    }
217}
218
219impl From<&String> for CheetahString {
220    #[inline]
221    fn from(s: &String) -> Self {
222        CheetahString::from_slice(s)
223    }
224}
225
226impl CheetahString {
227    #[inline]
228    pub const fn empty() -> Self {
229        CheetahString {
230            inner: InnerString::Inline(InlineStr::empty()),
231        }
232    }
233
234    #[inline]
235    pub fn new() -> Self {
236        CheetahString::default()
237    }
238
239    #[inline]
240    pub const fn from_static_str(s: &'static str) -> Self {
241        CheetahString {
242            inner: InnerString::Static(s),
243        }
244    }
245
246    /// Creates a `CheetahString` from a byte vector without validating UTF-8.
247    ///
248    /// # Safety
249    ///
250    /// The caller must guarantee that `s` contains valid UTF-8 for the entire
251    /// lifetime of the returned `CheetahString`.
252    #[inline]
253    pub unsafe fn from_utf8_unchecked_vec(s: Vec<u8>) -> Self {
254        CheetahString::from_validated_vec_unchecked(s)
255    }
256
257    #[inline]
258    fn from_validated_vec_unchecked(s: Vec<u8>) -> Self {
259        if s.len() <= INLINE_CAPACITY {
260            // SAFETY: Callers validate UTF-8 before reaching this helper.
261            let value = unsafe { str::from_utf8_unchecked(&s) };
262            let inline = InlineStr::from_str(value).expect("short str must fit inline storage");
263            return CheetahString {
264                inner: InnerString::Inline(inline),
265            };
266        }
267
268        // SAFETY: Callers validate UTF-8 before reaching this helper.
269        CheetahString::from_builder_string(unsafe { String::from_utf8_unchecked(s) })
270    }
271
272    /// Creates a `CheetahString` from a byte vector with UTF-8 validation.
273    ///
274    /// # Errors
275    ///
276    /// Returns an error if the bytes are not valid UTF-8.
277    ///
278    /// # Examples
279    ///
280    /// ```
281    /// use cheetah_string::CheetahString;
282    ///
283    /// let bytes = vec![104, 101, 108, 108, 111]; // "hello"
284    /// let s = CheetahString::try_from_vec(bytes).unwrap();
285    /// assert_eq!(s, "hello");
286    ///
287    /// let invalid = vec![0xFF, 0xFE];
288    /// assert!(CheetahString::try_from_vec(invalid).is_err());
289    /// ```
290    pub fn try_from_vec(v: Vec<u8>) -> Result<Self, Utf8Error> {
291        str::from_utf8(&v)?;
292        Ok(CheetahString::from_validated_vec_unchecked(v))
293    }
294
295    /// Creates a `CheetahString` from a byte slice with UTF-8 validation.
296    ///
297    /// # Errors
298    ///
299    /// Returns an error if the bytes are not valid UTF-8.
300    ///
301    /// # Examples
302    ///
303    /// ```
304    /// use cheetah_string::CheetahString;
305    ///
306    /// let bytes = b"hello";
307    /// let s = CheetahString::try_from_bytes(bytes).unwrap();
308    /// assert_eq!(s, "hello");
309    ///
310    /// let invalid = &[0xFF, 0xFE];
311    /// assert!(CheetahString::try_from_bytes(invalid).is_err());
312    /// ```
313    pub fn try_from_bytes(b: &[u8]) -> Result<Self, Utf8Error> {
314        let s = str::from_utf8(b)?;
315        Ok(CheetahString::from_slice(s))
316    }
317
318    /// Creates a `CheetahString` from a byte slice without validating UTF-8.
319    ///
320    /// # Safety
321    ///
322    /// The caller must guarantee that `b` contains valid UTF-8.
323    #[inline]
324    pub unsafe fn from_utf8_unchecked_bytes(b: &[u8]) -> Self {
325        // SAFETY: The caller guarantees that `b` contains valid UTF-8.
326        CheetahString::from_slice(unsafe { str::from_utf8_unchecked(b) })
327    }
328
329    /// Creates a `CheetahString` from a shared byte vector with UTF-8 validation.
330    ///
331    /// # Errors
332    ///
333    /// Returns an error if the bytes are not valid UTF-8.
334    #[inline]
335    pub fn try_from_arc_vec(s: Arc<Vec<u8>>) -> Result<Self, Utf8Error> {
336        match Arc::try_unwrap(s) {
337            Ok(v) => CheetahString::try_from_vec(v),
338            Err(s) => {
339                let s = str::from_utf8(s.as_slice())?;
340                Ok(CheetahString::from_slice(s))
341            }
342        }
343    }
344
345    /// Creates a `CheetahString` from a shared byte vector without validating UTF-8.
346    ///
347    /// # Safety
348    ///
349    /// The caller must guarantee that `s` contains valid UTF-8.
350    #[inline]
351    pub unsafe fn from_utf8_unchecked_arc_vec(s: Arc<Vec<u8>>) -> Self {
352        CheetahString::from_validated_arc_vec_unchecked(s)
353    }
354
355    #[inline]
356    fn from_validated_arc_vec_unchecked(s: Arc<Vec<u8>>) -> Self {
357        match Arc::try_unwrap(s) {
358            Ok(v) => CheetahString::from_validated_vec_unchecked(v),
359            Err(s) => {
360                // SAFETY: Callers validate UTF-8 before reaching this helper.
361                unsafe { CheetahString::from_utf8_unchecked_bytes(s.as_slice()) }
362            }
363        }
364    }
365
366    #[inline]
367    pub fn from_slice(s: &str) -> Self {
368        if let Some(inline) = InlineStr::from_str(s) {
369            CheetahString {
370                inner: InnerString::Inline(inline),
371            }
372        } else {
373            // Use Arc<str> for long borrowed strings to avoid the extra String header.
374            let arc_str: Arc<str> = Arc::from(s);
375            CheetahString {
376                inner: InnerString::Shared(arc_str),
377            }
378        }
379    }
380
381    #[inline]
382    pub fn from_string(s: String) -> Self {
383        CheetahString::from_string_owned(s)
384    }
385
386    /// Creates a `CheetahString` from an owned `String` while preserving
387    /// ownership and spare capacity for later mutation.
388    ///
389    /// This constructor is intended for builder-style paths that will continue
390    /// appending to the string. It keeps long strings in owned storage instead
391    /// of converting them to shared storage.
392    #[inline]
393    pub fn from_string_owned(s: String) -> Self {
394        CheetahString::from_builder_string(s)
395    }
396
397    /// Creates a `CheetahString` from an owned `String` using shared storage
398    /// for long immutable strings.
399    ///
400    /// New code that needs clone-cheap immutable strings should prefer
401    /// `CheetahStr`.
402    #[inline]
403    pub fn from_string_shared(s: String) -> Self {
404        if let Some(inline) = InlineStr::from_str(&s) {
405            CheetahString {
406                inner: InnerString::Inline(inline),
407            }
408        } else {
409            // Use Arc<str> for long strings to avoid double allocation
410            let arc_str: Arc<str> = s.into_boxed_str().into();
411            CheetahString {
412                inner: InnerString::Shared(arc_str),
413            }
414        }
415    }
416
417    #[inline]
418    fn from_builder_string(s: String) -> Self {
419        if s.len() <= INLINE_CAPACITY && s.capacity() <= INLINE_CAPACITY {
420            let inline = InlineStr::from_str(&s).expect("short String must fit inline storage");
421            CheetahString {
422                inner: InnerString::Inline(inline),
423            }
424        } else {
425            CheetahString {
426                inner: InnerString::Owned(s),
427            }
428        }
429    }
430
431    #[inline]
432    pub fn from_arc_string(s: Arc<String>) -> Self {
433        match Arc::try_unwrap(s) {
434            Ok(s) => CheetahString::from_builder_string(s),
435            Err(s) => CheetahString::from_slice(s.as_str()),
436        }
437    }
438
439    #[inline]
440    #[cfg(feature = "bytes")]
441    pub fn try_from_bytes_buf(b: bytes::Bytes) -> Result<Self, Utf8Error> {
442        str::from_utf8(b.as_ref())?;
443        Ok(CheetahString::from_validated_bytes_unchecked(b))
444    }
445
446    /// Creates a `CheetahString` from `bytes::Bytes` without validating UTF-8.
447    ///
448    /// # Safety
449    ///
450    /// The caller must guarantee that `b` contains valid UTF-8.
451    #[inline]
452    #[cfg(feature = "bytes")]
453    pub unsafe fn from_utf8_unchecked_bytes_buf(b: bytes::Bytes) -> Self {
454        CheetahString::from_validated_bytes_unchecked(b)
455    }
456
457    #[inline]
458    #[cfg(feature = "bytes")]
459    fn from_validated_bytes_unchecked(b: bytes::Bytes) -> Self {
460        // SAFETY: Callers validate UTF-8 before reaching this helper.
461        unsafe { CheetahString::from_utf8_unchecked_bytes(b.as_ref()) }
462    }
463
464    #[inline]
465    pub fn as_str(&self) -> &str {
466        match &self.inner {
467            InnerString::Inline(inline) => inline.as_str(),
468            InnerString::Static(s) => s,
469            InnerString::Shared(s) => s.as_ref(),
470            InnerString::Owned(s) => s.as_str(),
471        }
472    }
473
474    #[inline]
475    pub fn as_bytes(&self) -> &[u8] {
476        match &self.inner {
477            InnerString::Inline(inline) => inline.as_bytes(),
478            InnerString::Static(s) => s.as_bytes(),
479            InnerString::Shared(s) => s.as_bytes(),
480            InnerString::Owned(s) => s.as_bytes(),
481        }
482    }
483
484    #[inline]
485    pub fn len(&self) -> usize {
486        match &self.inner {
487            InnerString::Inline(inline) => inline.len(),
488            InnerString::Static(s) => s.len(),
489            InnerString::Shared(s) => s.len(),
490            InnerString::Owned(s) => s.len(),
491        }
492    }
493
494    #[inline]
495    pub fn is_empty(&self) -> bool {
496        match &self.inner {
497            InnerString::Inline(inline) => inline.is_empty(),
498            InnerString::Static(s) => s.is_empty(),
499            InnerString::Shared(s) => s.is_empty(),
500            InnerString::Owned(s) => s.is_empty(),
501        }
502    }
503
504    // Query methods - delegate to &str
505
506    /// Returns `true` if the string starts with the given pattern.
507    ///
508    /// When the `simd` feature is enabled, this method uses SIMD instructions
509    /// for improved performance on longer patterns.
510    ///
511    /// # Examples
512    ///
513    /// ```
514    /// use cheetah_string::CheetahString;
515    ///
516    /// let s = CheetahString::from("hello world");
517    /// assert!(s.starts_with("hello"));
518    /// assert!(!s.starts_with("world"));
519    /// assert!(s.starts_with('h'));
520    /// ```
521    #[inline]
522    pub fn starts_with<P: StrPattern>(&self, pat: P) -> bool {
523        match pat.as_str_pattern() {
524            StrPatternImpl::Char(c) => self.as_str().starts_with(c),
525            StrPatternImpl::Str(s) => {
526                #[cfg(all(feature = "simd", target_arch = "x86_64"))]
527                {
528                    if s.len() >= crate::simd::SIMD_THRESHOLD {
529                        return crate::simd::starts_with_bytes(self.as_bytes(), s.as_bytes());
530                    }
531                }
532
533                self.as_str().starts_with(s)
534            }
535        }
536    }
537
538    /// Returns `true` if the string starts with the given character.
539    ///
540    /// # Examples
541    ///
542    /// ```
543    /// use cheetah_string::CheetahString;
544    ///
545    /// let s = CheetahString::from("hello world");
546    /// assert!(s.starts_with_char('h'));
547    /// assert!(!s.starts_with_char('w'));
548    /// ```
549    #[inline]
550    pub fn starts_with_char(&self, pat: char) -> bool {
551        self.as_str().starts_with(pat)
552    }
553
554    /// Returns `true` if the string ends with the given pattern.
555    ///
556    /// When the `simd` feature is enabled, this method uses SIMD instructions
557    /// for improved performance on longer patterns.
558    ///
559    /// # Examples
560    ///
561    /// ```
562    /// use cheetah_string::CheetahString;
563    ///
564    /// let s = CheetahString::from("hello world");
565    /// assert!(s.ends_with("world"));
566    /// assert!(!s.ends_with("hello"));
567    /// assert!(s.ends_with('d'));
568    /// ```
569    #[inline]
570    pub fn ends_with<P: StrPattern>(&self, pat: P) -> bool {
571        match pat.as_str_pattern() {
572            StrPatternImpl::Char(c) => self.as_str().ends_with(c),
573            StrPatternImpl::Str(s) => {
574                #[cfg(all(feature = "simd", target_arch = "x86_64"))]
575                {
576                    if s.len() >= crate::simd::SIMD_THRESHOLD {
577                        return crate::simd::ends_with_bytes(self.as_bytes(), s.as_bytes());
578                    }
579                }
580
581                self.as_str().ends_with(s)
582            }
583        }
584    }
585
586    /// Returns `true` if the string ends with the given character.
587    ///
588    /// # Examples
589    ///
590    /// ```
591    /// use cheetah_string::CheetahString;
592    ///
593    /// let s = CheetahString::from("hello world");
594    /// assert!(s.ends_with_char('d'));
595    /// assert!(!s.ends_with_char('h'));
596    /// ```
597    #[inline]
598    pub fn ends_with_char(&self, pat: char) -> bool {
599        self.as_str().ends_with(pat)
600    }
601
602    /// Returns `true` if the string contains the given pattern.
603    ///
604    /// This method uses the `memchr`/`memmem` search backend. The `simd`
605    /// feature currently accelerates equality, prefix, and suffix checks, not
606    /// substring search.
607    ///
608    /// # Examples
609    ///
610    /// ```
611    /// use cheetah_string::CheetahString;
612    ///
613    /// let s = CheetahString::from("hello world");
614    /// assert!(s.contains("llo"));
615    /// assert!(!s.contains("xyz"));
616    /// assert!(s.contains('o'));
617    /// ```
618    #[inline]
619    pub fn contains<P: StrPattern>(&self, pat: P) -> bool {
620        match pat.as_str_pattern() {
621            StrPatternImpl::Char(c) => self.as_str().contains(c),
622            StrPatternImpl::Str(s) => {
623                crate::search::find_bytes(self.as_bytes(), s.as_bytes()).is_some()
624            }
625        }
626    }
627
628    /// Returns `true` if the string contains the given character.
629    ///
630    /// # Examples
631    ///
632    /// ```
633    /// use cheetah_string::CheetahString;
634    ///
635    /// let s = CheetahString::from("hello world");
636    /// assert!(s.contains_char('o'));
637    /// assert!(!s.contains_char('x'));
638    /// ```
639    #[inline]
640    pub fn contains_char(&self, pat: char) -> bool {
641        self.as_str().contains(pat)
642    }
643
644    /// Returns the byte index of the first occurrence of the pattern, or `None` if not found.
645    ///
646    /// This method uses the `memchr`/`memmem` search backend. The `simd`
647    /// feature currently accelerates equality, prefix, and suffix checks, not
648    /// substring search.
649    ///
650    /// # Examples
651    ///
652    /// ```
653    /// use cheetah_string::CheetahString;
654    ///
655    /// let s = CheetahString::from("hello world");
656    /// assert_eq!(s.find("world"), Some(6));
657    /// assert_eq!(s.find("xyz"), None);
658    /// ```
659    #[inline]
660    pub fn find<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
661        let pat = pat.as_ref();
662        crate::search::find_bytes(self.as_bytes(), pat.as_bytes())
663    }
664
665    /// Returns the byte index of the last occurrence of the pattern, or `None` if not found.
666    ///
667    /// # Examples
668    ///
669    /// ```
670    /// use cheetah_string::CheetahString;
671    ///
672    /// let s = CheetahString::from("hello hello");
673    /// assert_eq!(s.rfind("hello"), Some(6));
674    /// ```
675    #[inline]
676    pub fn rfind<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
677        crate::search::rfind_bytes(self.as_bytes(), pat.as_ref().as_bytes())
678    }
679
680    /// Returns a string slice with leading and trailing whitespace removed.
681    ///
682    /// # Examples
683    ///
684    /// ```
685    /// use cheetah_string::CheetahString;
686    ///
687    /// let s = CheetahString::from("  hello  ");
688    /// assert_eq!(s.trim(), "hello");
689    /// ```
690    #[inline]
691    pub fn trim(&self) -> &str {
692        self.as_str().trim()
693    }
694
695    /// Returns a string slice with leading whitespace removed.
696    ///
697    /// # Examples
698    ///
699    /// ```
700    /// use cheetah_string::CheetahString;
701    ///
702    /// let s = CheetahString::from("  hello");
703    /// assert_eq!(s.trim_start(), "hello");
704    /// ```
705    #[inline]
706    pub fn trim_start(&self) -> &str {
707        self.as_str().trim_start()
708    }
709
710    /// Returns a string slice with trailing whitespace removed.
711    ///
712    /// # Examples
713    ///
714    /// ```
715    /// use cheetah_string::CheetahString;
716    ///
717    /// let s = CheetahString::from("hello  ");
718    /// assert_eq!(s.trim_end(), "hello");
719    /// ```
720    #[inline]
721    pub fn trim_end(&self) -> &str {
722        self.as_str().trim_end()
723    }
724
725    /// Splits the string by the given pattern.
726    ///
727    /// # Examples
728    ///
729    /// ```
730    /// use cheetah_string::CheetahString;
731    ///
732    /// let s = CheetahString::from("a,b,c");
733    /// let parts: Vec<&str> = s.split(",").collect();
734    /// assert_eq!(parts, vec!["a", "b", "c"]);
735    /// let parts2: Vec<&str> = s.split(',').collect();
736    /// assert_eq!(parts2, vec!["a", "b", "c"]);
737    /// ```
738    #[inline]
739    pub fn split<'a, P>(&'a self, pat: P) -> SplitWrapper<'a>
740    where
741        P: SplitPattern<'a>,
742    {
743        pat.split_str(self.as_str())
744    }
745
746    /// Returns an iterator over the lines of the string.
747    ///
748    /// # Examples
749    ///
750    /// ```
751    /// use cheetah_string::CheetahString;
752    ///
753    /// let s = CheetahString::from("line1\nline2\nline3");
754    /// let lines: Vec<&str> = s.lines().collect();
755    /// assert_eq!(lines, vec!["line1", "line2", "line3"]);
756    /// ```
757    #[inline]
758    pub fn lines(&self) -> impl Iterator<Item = &str> {
759        self.as_str().lines()
760    }
761
762    /// Returns an iterator over the characters of the string.
763    ///
764    /// # Examples
765    ///
766    /// ```
767    /// use cheetah_string::CheetahString;
768    ///
769    /// let s = CheetahString::from("hello");
770    /// let chars: Vec<char> = s.chars().collect();
771    /// assert_eq!(chars, vec!['h', 'e', 'l', 'l', 'o']);
772    /// let reversed: Vec<char> = s.chars().rev().collect();
773    /// assert_eq!(reversed, vec!['o', 'l', 'l', 'e', 'h']);
774    /// ```
775    #[inline]
776    pub fn chars(&self) -> str::Chars<'_> {
777        self.as_str().chars()
778    }
779
780    // Transformation methods - create new CheetahString
781
782    /// Returns a new `CheetahString` with all characters converted to uppercase.
783    ///
784    /// # Examples
785    ///
786    /// ```
787    /// use cheetah_string::CheetahString;
788    ///
789    /// let s = CheetahString::from("hello");
790    /// assert_eq!(s.to_uppercase(), "HELLO");
791    /// ```
792    #[inline]
793    pub fn to_uppercase(&self) -> CheetahString {
794        CheetahString::from_string(self.as_str().to_uppercase())
795    }
796
797    /// Returns a new `CheetahString` with all characters converted to lowercase.
798    ///
799    /// # Examples
800    ///
801    /// ```
802    /// use cheetah_string::CheetahString;
803    ///
804    /// let s = CheetahString::from("HELLO");
805    /// assert_eq!(s.to_lowercase(), "hello");
806    /// ```
807    #[inline]
808    pub fn to_lowercase(&self) -> CheetahString {
809        CheetahString::from_string(self.as_str().to_lowercase())
810    }
811
812    /// Replaces all occurrences of a pattern with another string.
813    ///
814    /// # Examples
815    ///
816    /// ```
817    /// use cheetah_string::CheetahString;
818    ///
819    /// let s = CheetahString::from("hello world");
820    /// assert_eq!(s.replace("world", "rust"), "hello rust");
821    /// ```
822    #[inline]
823    pub fn replace<P: AsRef<str>>(&self, from: P, to: &str) -> CheetahString {
824        CheetahString::from_string(self.as_str().replace(from.as_ref(), to))
825    }
826
827    /// Returns a new `CheetahString` with the specified range replaced.
828    ///
829    /// # Examples
830    ///
831    /// ```
832    /// use cheetah_string::CheetahString;
833    ///
834    /// let s = CheetahString::from("hello world");
835    /// assert_eq!(s.replacen("l", "L", 1), "heLlo world");
836    /// ```
837    #[inline]
838    pub fn replacen<P: AsRef<str>>(&self, from: P, to: &str, count: usize) -> CheetahString {
839        CheetahString::from_string(self.as_str().replacen(from.as_ref(), to, count))
840    }
841
842    /// Returns a substring as a new `CheetahString`.
843    ///
844    /// # Panics
845    ///
846    /// Panics if the range is out of bounds, inverted, or not on valid UTF-8
847    /// character boundaries. Use [`CheetahString::try_substring`] for a
848    /// recoverable error.
849    ///
850    /// # Examples
851    ///
852    /// ```
853    /// use cheetah_string::CheetahString;
854    ///
855    /// let s = CheetahString::from("hello world");
856    /// assert_eq!(s.substring(0, 5), "hello");
857    /// assert_eq!(s.substring(6, 11), "world");
858    /// ```
859    #[inline]
860    pub fn substring(&self, start: usize, end: usize) -> CheetahString {
861        self.try_substring(start, end)
862            .expect("substring range must be in bounds and on UTF-8 character boundaries")
863    }
864
865    /// Returns a substring as a new `CheetahString`, or a public error when
866    /// the requested range is invalid.
867    ///
868    /// # Examples
869    ///
870    /// ```
871    /// use cheetah_string::CheetahString;
872    ///
873    /// let s = CheetahString::from("hello world");
874    /// assert_eq!(s.try_substring(0, 5).unwrap(), "hello");
875    /// assert!(s.try_substring(0, 20).is_err());
876    /// ```
877    #[inline]
878    pub fn try_substring(&self, start: usize, end: usize) -> crate::Result<CheetahString> {
879        let value = self.as_str();
880        let len = value.len();
881
882        if start > end {
883            return Err(crate::Error::InvalidRange { start, end });
884        }
885
886        if start > len {
887            return Err(crate::Error::IndexOutOfBounds { index: start, len });
888        }
889
890        if end > len {
891            return Err(crate::Error::IndexOutOfBounds { index: end, len });
892        }
893
894        if !value.is_char_boundary(start) {
895            return Err(crate::Error::InvalidCharBoundary { index: start });
896        }
897
898        if !value.is_char_boundary(end) {
899            return Err(crate::Error::InvalidCharBoundary { index: end });
900        }
901
902        Ok(CheetahString::from_slice(&value[start..end]))
903    }
904
905    /// Repeats the string `n` times.
906    ///
907    /// # Examples
908    ///
909    /// ```
910    /// use cheetah_string::CheetahString;
911    ///
912    /// let s = CheetahString::from("abc");
913    /// assert_eq!(s.repeat(3), "abcabcabc");
914    /// ```
915    #[inline]
916    pub fn repeat(&self, n: usize) -> CheetahString {
917        CheetahString::from_string(self.as_str().repeat(n))
918    }
919
920    // Incremental building methods
921
922    /// Creates a new `CheetahString` with the specified capacity.
923    ///
924    /// The string will be able to hold at least `capacity` bytes without reallocating.
925    /// If `capacity` is less than or equal to the inline capacity (23 bytes),
926    /// an empty inline string is returned.
927    ///
928    /// # Examples
929    ///
930    /// ```
931    /// use cheetah_string::CheetahString;
932    ///
933    /// let mut s = CheetahString::with_capacity(100);
934    /// s.push_str("hello");
935    /// assert_eq!(s, "hello");
936    /// ```
937    #[inline]
938    pub fn with_capacity(capacity: usize) -> Self {
939        if capacity <= INLINE_CAPACITY {
940            CheetahString::empty()
941        } else {
942            CheetahString::from_builder_string(String::with_capacity(capacity))
943        }
944    }
945
946    #[inline]
947    fn push_str_internal(&mut self, string: &str) {
948        if string.is_empty() {
949            return;
950        }
951
952        match &mut self.inner {
953            InnerString::Inline(inline) => {
954                if inline.push_str(string) {
955                    return;
956                }
957            }
958            InnerString::Owned(s) => {
959                s.push_str(string);
960                return;
961            }
962            _ => {}
963        }
964
965        let total_len = self.len() + string.len();
966        let mut result = String::with_capacity(total_len);
967        result.push_str(self.as_str());
968        result.push_str(string);
969        *self = CheetahString::from_builder_string(result);
970    }
971
972    /// Appends a string slice to the end of this `CheetahString`.
973    ///
974    /// This method is optimized for incremental building and will:
975    /// - Mutate inline storage when possible
976    /// - Mutate owned heap storage in-place when capacity allows
977    /// - Only allocate when necessary
978    ///
979    /// # Examples
980    ///
981    /// ```
982    /// use cheetah_string::CheetahString;
983    ///
984    /// let mut s = CheetahString::from("Hello");
985    /// s.push_str(" ");
986    /// s.push_str("World");
987    /// assert_eq!(s, "Hello World");
988    /// ```
989    #[inline]
990    pub fn push_str(&mut self, string: &str) {
991        self.push_str_internal(string);
992    }
993
994    /// Reserves capacity for at least `additional` more bytes.
995    ///
996    /// This method will modify the internal representation if needed to ensure
997    /// that the string can hold at least `additional` more bytes without reallocating.
998    ///
999    /// # Examples
1000    ///
1001    /// ```
1002    /// use cheetah_string::CheetahString;
1003    ///
1004    /// let mut s = CheetahString::from("hello");
1005    /// s.reserve(100);
1006    /// s.push_str(" world");
1007    /// ```
1008    #[inline]
1009    pub fn reserve(&mut self, additional: usize) {
1010        if additional == 0 {
1011            return;
1012        }
1013
1014        match &mut self.inner {
1015            InnerString::Inline(inline) if inline.len() + additional <= INLINE_CAPACITY => {
1016                return;
1017            }
1018            InnerString::Inline(_) => {}
1019            InnerString::Owned(s) => {
1020                s.reserve(additional);
1021                return;
1022            }
1023            _ => {}
1024        }
1025
1026        let new_len = self.len() + additional;
1027        let mut s = String::with_capacity(new_len);
1028        s.push_str(self.as_str());
1029        *self = CheetahString::from_builder_string(s);
1030    }
1031}
1032
1033impl PartialEq for CheetahString {
1034    #[inline]
1035    fn eq(&self, other: &Self) -> bool {
1036        #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1037        {
1038            crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1039        }
1040        #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1041        {
1042            self.as_str() == other.as_str()
1043        }
1044    }
1045}
1046
1047impl PartialEq<str> for CheetahString {
1048    #[inline]
1049    fn eq(&self, other: &str) -> bool {
1050        #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1051        {
1052            crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1053        }
1054        #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1055        {
1056            self.as_str() == other
1057        }
1058    }
1059}
1060
1061impl PartialEq<String> for CheetahString {
1062    #[inline]
1063    fn eq(&self, other: &String) -> bool {
1064        #[cfg(all(feature = "simd", target_arch = "x86_64"))]
1065        {
1066            crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
1067        }
1068        #[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
1069        {
1070            self.as_str() == other.as_str()
1071        }
1072    }
1073}
1074
1075impl PartialEq<Vec<u8>> for CheetahString {
1076    #[inline]
1077    fn eq(&self, other: &Vec<u8>) -> bool {
1078        self.as_bytes() == other.as_slice()
1079    }
1080}
1081
1082impl<'a> PartialEq<&'a str> for CheetahString {
1083    #[inline]
1084    fn eq(&self, other: &&'a str) -> bool {
1085        self.as_str() == *other
1086    }
1087}
1088
1089impl PartialEq<CheetahString> for str {
1090    #[inline]
1091    fn eq(&self, other: &CheetahString) -> bool {
1092        self == other.as_str()
1093    }
1094}
1095
1096impl PartialEq<CheetahString> for String {
1097    #[inline]
1098    fn eq(&self, other: &CheetahString) -> bool {
1099        self.as_str() == other.as_str()
1100    }
1101}
1102
1103impl PartialEq<CheetahString> for &str {
1104    #[inline]
1105    fn eq(&self, other: &CheetahString) -> bool {
1106        *self == other.as_str()
1107    }
1108}
1109
1110impl Eq for CheetahString {}
1111
1112impl PartialOrd for CheetahString {
1113    #[inline]
1114    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1115        Some(self.cmp(other))
1116    }
1117}
1118
1119impl Ord for CheetahString {
1120    #[inline]
1121    fn cmp(&self, other: &Self) -> Ordering {
1122        self.as_str().cmp(other.as_str())
1123    }
1124}
1125
1126impl Hash for CheetahString {
1127    #[inline]
1128    fn hash<H: Hasher>(&self, state: &mut H) {
1129        self.as_str().hash(state);
1130    }
1131}
1132
1133impl Display for CheetahString {
1134    #[inline]
1135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1136        self.as_str().fmt(f)
1137    }
1138}
1139
1140impl fmt::Debug for CheetahString {
1141    #[inline]
1142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143        fmt::Debug::fmt(self.as_str(), f)
1144    }
1145}
1146
1147impl Borrow<str> for CheetahString {
1148    #[inline]
1149    fn borrow(&self) -> &str {
1150        self.as_str()
1151    }
1152}
1153
1154// Add trait implementations for string concatenation
1155
1156impl Add<&str> for CheetahString {
1157    type Output = CheetahString;
1158
1159    /// Concatenates a `CheetahString` with a string slice.
1160    ///
1161    /// # Examples
1162    ///
1163    /// ```
1164    /// use cheetah_string::CheetahString;
1165    ///
1166    /// let s = CheetahString::from("Hello");
1167    /// let result = s + " World";
1168    /// assert_eq!(result, "Hello World");
1169    /// ```
1170    #[inline]
1171    fn add(mut self, rhs: &str) -> Self::Output {
1172        self.push_str_internal(rhs);
1173        self
1174    }
1175}
1176
1177impl Add<&CheetahString> for CheetahString {
1178    type Output = CheetahString;
1179
1180    /// Concatenates two `CheetahString` values.
1181    ///
1182    /// # Examples
1183    ///
1184    /// ```
1185    /// use cheetah_string::CheetahString;
1186    ///
1187    /// let s1 = CheetahString::from("Hello");
1188    /// let s2 = CheetahString::from(" World");
1189    /// let result = s1 + &s2;
1190    /// assert_eq!(result, "Hello World");
1191    /// ```
1192    #[inline]
1193    fn add(mut self, rhs: &CheetahString) -> Self::Output {
1194        self.push_str_internal(rhs.as_str());
1195        self
1196    }
1197}
1198
1199impl Add<String> for CheetahString {
1200    type Output = CheetahString;
1201
1202    /// Concatenates a `CheetahString` with a `String`.
1203    ///
1204    /// # Examples
1205    ///
1206    /// ```
1207    /// use cheetah_string::CheetahString;
1208    ///
1209    /// let s = CheetahString::from("Hello");
1210    /// let result = s + String::from(" World");
1211    /// assert_eq!(result, "Hello World");
1212    /// ```
1213    #[inline]
1214    fn add(mut self, rhs: String) -> Self::Output {
1215        if self.is_empty() {
1216            return CheetahString::from_string_owned(rhs);
1217        }
1218
1219        self.push_str_internal(&rhs);
1220        self
1221    }
1222}
1223
1224impl AddAssign<&str> for CheetahString {
1225    /// Appends a string slice to a `CheetahString`.
1226    ///
1227    /// # Examples
1228    ///
1229    /// ```
1230    /// use cheetah_string::CheetahString;
1231    ///
1232    /// let mut s = CheetahString::from("Hello");
1233    /// s += " World";
1234    /// assert_eq!(s, "Hello World");
1235    /// ```
1236    #[inline]
1237    fn add_assign(&mut self, rhs: &str) {
1238        self.push_str_internal(rhs);
1239    }
1240}
1241
1242impl AddAssign<&CheetahString> for CheetahString {
1243    /// Appends a `CheetahString` to another `CheetahString`.
1244    ///
1245    /// # Examples
1246    ///
1247    /// ```
1248    /// use cheetah_string::CheetahString;
1249    ///
1250    /// let mut s1 = CheetahString::from("Hello");
1251    /// let s2 = CheetahString::from(" World");
1252    /// s1 += &s2;
1253    /// assert_eq!(s1, "Hello World");
1254    /// ```
1255    #[inline]
1256    fn add_assign(&mut self, rhs: &CheetahString) {
1257        self.push_str_internal(rhs.as_str());
1258    }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263    use super::*;
1264    use alloc::{format, vec};
1265
1266    #[test]
1267    fn with_capacity_above_inline_uses_heap_storage() {
1268        let s = CheetahString::with_capacity(INLINE_CAPACITY + 8);
1269
1270        match &s.inner {
1271            InnerString::Owned(inner) => {
1272                assert!(inner.capacity() >= INLINE_CAPACITY + 8);
1273            }
1274            other => panic!(
1275                "expected heap-backed storage from with_capacity, got {:?}",
1276                core::mem::discriminant(other)
1277            ),
1278        }
1279    }
1280
1281    #[test]
1282    fn push_str_promotes_builder_growth_to_owned_storage() {
1283        let suffix = "a".repeat(INLINE_CAPACITY);
1284        let expected = format!("hello{suffix}");
1285        let mut s = CheetahString::from("hello");
1286
1287        s.push_str(&suffix);
1288
1289        match &s.inner {
1290            InnerString::Owned(inner) => {
1291                assert_eq!(inner.as_str(), expected.as_str());
1292                assert!(inner.capacity() >= expected.len());
1293            }
1294            other => panic!(
1295                "expected owned heap storage after builder growth, got {:?}",
1296                core::mem::discriminant(other)
1297            ),
1298        }
1299    }
1300
1301    #[test]
1302    fn long_borrowed_str_uses_shared_storage() {
1303        let value = "a".repeat(INLINE_CAPACITY + 1);
1304        let s = CheetahString::from_slice(&value);
1305
1306        match &s.inner {
1307            InnerString::Shared(inner) => assert_eq!(inner.as_ref(), value.as_str()),
1308            other => panic!(
1309                "expected Shared for long borrowed input, got {:?}",
1310                core::mem::discriminant(other)
1311            ),
1312        }
1313    }
1314
1315    #[test]
1316    fn try_from_vec_short_input_uses_inline_storage() {
1317        let s = CheetahString::try_from_vec(b"hello".to_vec()).expect("valid utf-8");
1318
1319        match &s.inner {
1320            InnerString::Inline(inline) => {
1321                assert_eq!(inline.len(), 5);
1322                assert_eq!(inline.as_bytes(), b"hello");
1323            }
1324            other => panic!(
1325                "expected inline storage for short validated Vec<u8>, got {:?}",
1326                core::mem::discriminant(other)
1327            ),
1328        }
1329    }
1330
1331    #[test]
1332    fn long_vec_conversion_uses_owned_storage() {
1333        let value = "a".repeat(INLINE_CAPACITY + 1).into_bytes();
1334        let s = CheetahString::try_from_vec(value).expect("valid utf-8");
1335
1336        match &s.inner {
1337            InnerString::Owned(inner) => {
1338                assert_eq!(inner.len(), INLINE_CAPACITY + 1);
1339                assert_eq!(inner.as_bytes(), vec![b'a'; INLINE_CAPACITY + 1].as_slice());
1340            }
1341            other => panic!(
1342                "expected Owned for long Vec<u8> conversion, got {:?}",
1343                core::mem::discriminant(other)
1344            ),
1345        }
1346    }
1347}