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