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 ends with the given pattern.
472 ///
473 /// # Examples
474 ///
475 /// ```
476 /// use cheetah_string::CheetahString;
477 ///
478 /// let s = CheetahString::from("hello world");
479 /// assert!(s.ends_with("world"));
480 /// assert!(!s.ends_with("hello"));
481 /// ```
482 #[inline]
483 pub fn ends_with<P: AsRef<str>>(&self, pat: P) -> bool {
484 self.as_str().ends_with(pat.as_ref())
485 }
486
487 /// Returns `true` if the string contains the given pattern.
488 ///
489 /// # Examples
490 ///
491 /// ```
492 /// use cheetah_string::CheetahString;
493 ///
494 /// let s = CheetahString::from("hello world");
495 /// assert!(s.contains("llo"));
496 /// assert!(!s.contains("xyz"));
497 /// ```
498 #[inline]
499 pub fn contains<P: AsRef<str>>(&self, pat: P) -> bool {
500 self.as_str().contains(pat.as_ref())
501 }
502
503 /// Returns the byte index of the first occurrence of the pattern, or `None` if not found.
504 ///
505 /// # Examples
506 ///
507 /// ```
508 /// use cheetah_string::CheetahString;
509 ///
510 /// let s = CheetahString::from("hello world");
511 /// assert_eq!(s.find("world"), Some(6));
512 /// assert_eq!(s.find("xyz"), None);
513 /// ```
514 #[inline]
515 pub fn find<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
516 self.as_str().find(pat.as_ref())
517 }
518
519 /// Returns the byte index of the last occurrence of the pattern, or `None` if not found.
520 ///
521 /// # Examples
522 ///
523 /// ```
524 /// use cheetah_string::CheetahString;
525 ///
526 /// let s = CheetahString::from("hello hello");
527 /// assert_eq!(s.rfind("hello"), Some(6));
528 /// ```
529 #[inline]
530 pub fn rfind<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
531 self.as_str().rfind(pat.as_ref())
532 }
533
534 /// Returns a string slice with leading and trailing whitespace removed.
535 ///
536 /// # Examples
537 ///
538 /// ```
539 /// use cheetah_string::CheetahString;
540 ///
541 /// let s = CheetahString::from(" hello ");
542 /// assert_eq!(s.trim(), "hello");
543 /// ```
544 #[inline]
545 pub fn trim(&self) -> &str {
546 self.as_str().trim()
547 }
548
549 /// Returns a string slice with leading whitespace removed.
550 ///
551 /// # Examples
552 ///
553 /// ```
554 /// use cheetah_string::CheetahString;
555 ///
556 /// let s = CheetahString::from(" hello");
557 /// assert_eq!(s.trim_start(), "hello");
558 /// ```
559 #[inline]
560 pub fn trim_start(&self) -> &str {
561 self.as_str().trim_start()
562 }
563
564 /// Returns a string slice with trailing whitespace removed.
565 ///
566 /// # Examples
567 ///
568 /// ```
569 /// use cheetah_string::CheetahString;
570 ///
571 /// let s = CheetahString::from("hello ");
572 /// assert_eq!(s.trim_end(), "hello");
573 /// ```
574 #[inline]
575 pub fn trim_end(&self) -> &str {
576 self.as_str().trim_end()
577 }
578
579 /// Splits the string by the given pattern.
580 ///
581 /// # Examples
582 ///
583 /// ```
584 /// use cheetah_string::CheetahString;
585 ///
586 /// let s = CheetahString::from("a,b,c");
587 /// let parts: Vec<&str> = s.split(",").collect();
588 /// assert_eq!(parts, vec!["a", "b", "c"]);
589 /// ```
590 #[inline]
591 pub fn split<'a>(&'a self, pat: &'a str) -> impl Iterator<Item = &'a str> {
592 self.as_str().split(pat)
593 }
594
595 /// Returns an iterator over the lines of the string.
596 ///
597 /// # Examples
598 ///
599 /// ```
600 /// use cheetah_string::CheetahString;
601 ///
602 /// let s = CheetahString::from("line1\nline2\nline3");
603 /// let lines: Vec<&str> = s.lines().collect();
604 /// assert_eq!(lines, vec!["line1", "line2", "line3"]);
605 /// ```
606 #[inline]
607 pub fn lines(&self) -> impl Iterator<Item = &str> {
608 self.as_str().lines()
609 }
610
611 /// Returns an iterator over the characters of the string.
612 ///
613 /// # Examples
614 ///
615 /// ```
616 /// use cheetah_string::CheetahString;
617 ///
618 /// let s = CheetahString::from("hello");
619 /// let chars: Vec<char> = s.chars().collect();
620 /// assert_eq!(chars, vec!['h', 'e', 'l', 'l', 'o']);
621 /// ```
622 #[inline]
623 pub fn chars(&self) -> impl Iterator<Item = char> + '_ {
624 self.as_str().chars()
625 }
626
627 // Transformation methods - create new CheetahString
628
629 /// Returns a new `CheetahString` with all characters converted to uppercase.
630 ///
631 /// # Examples
632 ///
633 /// ```
634 /// use cheetah_string::CheetahString;
635 ///
636 /// let s = CheetahString::from("hello");
637 /// assert_eq!(s.to_uppercase(), "HELLO");
638 /// ```
639 #[inline]
640 pub fn to_uppercase(&self) -> CheetahString {
641 CheetahString::from_string(self.as_str().to_uppercase())
642 }
643
644 /// Returns a new `CheetahString` with all characters converted to lowercase.
645 ///
646 /// # Examples
647 ///
648 /// ```
649 /// use cheetah_string::CheetahString;
650 ///
651 /// let s = CheetahString::from("HELLO");
652 /// assert_eq!(s.to_lowercase(), "hello");
653 /// ```
654 #[inline]
655 pub fn to_lowercase(&self) -> CheetahString {
656 CheetahString::from_string(self.as_str().to_lowercase())
657 }
658
659 /// Replaces all occurrences of a pattern with another string.
660 ///
661 /// # Examples
662 ///
663 /// ```
664 /// use cheetah_string::CheetahString;
665 ///
666 /// let s = CheetahString::from("hello world");
667 /// assert_eq!(s.replace("world", "rust"), "hello rust");
668 /// ```
669 #[inline]
670 pub fn replace<P: AsRef<str>>(&self, from: P, to: &str) -> CheetahString {
671 CheetahString::from_string(self.as_str().replace(from.as_ref(), to))
672 }
673
674 /// Returns a new `CheetahString` with the specified range replaced.
675 ///
676 /// # Examples
677 ///
678 /// ```
679 /// use cheetah_string::CheetahString;
680 ///
681 /// let s = CheetahString::from("hello world");
682 /// assert_eq!(s.replacen("l", "L", 1), "heLlo world");
683 /// ```
684 #[inline]
685 pub fn replacen<P: AsRef<str>>(&self, from: P, to: &str, count: usize) -> CheetahString {
686 CheetahString::from_string(self.as_str().replacen(from.as_ref(), to, count))
687 }
688
689 /// Returns a substring as a new `CheetahString`.
690 ///
691 /// # Panics
692 ///
693 /// Panics if the indices are not on valid UTF-8 character boundaries.
694 ///
695 /// # Examples
696 ///
697 /// ```
698 /// use cheetah_string::CheetahString;
699 ///
700 /// let s = CheetahString::from("hello world");
701 /// assert_eq!(s.substring(0, 5), "hello");
702 /// assert_eq!(s.substring(6, 11), "world");
703 /// ```
704 #[inline]
705 pub fn substring(&self, start: usize, end: usize) -> CheetahString {
706 CheetahString::from_slice(&self.as_str()[start..end])
707 }
708
709 /// Repeats the string `n` times.
710 ///
711 /// # Examples
712 ///
713 /// ```
714 /// use cheetah_string::CheetahString;
715 ///
716 /// let s = CheetahString::from("abc");
717 /// assert_eq!(s.repeat(3), "abcabcabc");
718 /// ```
719 #[inline]
720 pub fn repeat(&self, n: usize) -> CheetahString {
721 CheetahString::from_string(self.as_str().repeat(n))
722 }
723
724 // Incremental building methods
725
726 /// Creates a new `CheetahString` with the specified capacity.
727 ///
728 /// The string will be able to hold at least `capacity` bytes without reallocating.
729 /// If `capacity` is less than or equal to the inline capacity (23 bytes),
730 /// an empty inline string is returned.
731 ///
732 /// # Examples
733 ///
734 /// ```
735 /// use cheetah_string::CheetahString;
736 ///
737 /// let mut s = CheetahString::with_capacity(100);
738 /// s.push_str("hello");
739 /// assert_eq!(s, "hello");
740 /// ```
741 #[inline]
742 pub fn with_capacity(capacity: usize) -> Self {
743 if capacity <= INLINE_CAPACITY {
744 CheetahString::empty()
745 } else {
746 CheetahString::from_string(String::with_capacity(capacity))
747 }
748 }
749
750 /// Appends a string slice to the end of this `CheetahString`.
751 ///
752 /// This method is optimized for incremental building and will:
753 /// - Mutate inline storage when possible
754 /// - Mutate unique Arc<String> in-place when available
755 /// - Only allocate when necessary
756 ///
757 /// # Examples
758 ///
759 /// ```
760 /// use cheetah_string::CheetahString;
761 ///
762 /// let mut s = CheetahString::from("Hello");
763 /// s.push_str(" ");
764 /// s.push_str("World");
765 /// assert_eq!(s, "Hello World");
766 /// ```
767 #[inline]
768 pub fn push_str(&mut self, string: &str) {
769 *self += string;
770 }
771
772 /// Reserves capacity for at least `additional` more bytes.
773 ///
774 /// This method will modify the internal representation if needed to ensure
775 /// that the string can hold at least `additional` more bytes without reallocating.
776 ///
777 /// # Examples
778 ///
779 /// ```
780 /// use cheetah_string::CheetahString;
781 ///
782 /// let mut s = CheetahString::from("hello");
783 /// s.reserve(100);
784 /// s.push_str(" world");
785 /// ```
786 #[inline]
787 pub fn reserve(&mut self, additional: usize) {
788 let new_len = self.len() + additional;
789
790 // If it still fits inline, nothing to do
791 if new_len <= INLINE_CAPACITY {
792 return;
793 }
794
795 match &mut self.inner {
796 InnerString::Inline { .. } => {
797 // Convert inline to Arc<String> with capacity
798 let mut s = String::with_capacity(new_len);
799 s.push_str(self.as_str());
800 *self = CheetahString {
801 inner: InnerString::ArcString(Arc::new(s)),
802 };
803 }
804 InnerString::ArcString(arc) if Arc::strong_count(arc) == 1 => {
805 // Reserve in the unique Arc<String>
806 if let Some(s) = Arc::get_mut(arc) {
807 s.reserve(additional);
808 }
809 }
810 InnerString::StaticStr(_) | InnerString::ArcStr(_) => {
811 // Convert to Arc<String> with capacity
812 let mut s = String::with_capacity(new_len);
813 s.push_str(self.as_str());
814 *self = CheetahString {
815 inner: InnerString::ArcString(Arc::new(s)),
816 };
817 }
818 _ => {
819 // For shared Arc or other types, convert if needed
820 if Arc::strong_count(match &self.inner {
821 InnerString::ArcString(arc) => arc,
822 _ => return,
823 }) > 1
824 {
825 let mut s = String::with_capacity(new_len);
826 s.push_str(self.as_str());
827 *self = CheetahString {
828 inner: InnerString::ArcString(Arc::new(s)),
829 };
830 }
831 }
832 }
833 }
834}
835
836impl PartialEq for CheetahString {
837 #[inline]
838 fn eq(&self, other: &Self) -> bool {
839 self.as_str() == other.as_str()
840 }
841}
842
843impl PartialEq<str> for CheetahString {
844 #[inline]
845 fn eq(&self, other: &str) -> bool {
846 self.as_str() == other
847 }
848}
849
850impl PartialEq<String> for CheetahString {
851 #[inline]
852 fn eq(&self, other: &String) -> bool {
853 self.as_str() == other.as_str()
854 }
855}
856
857impl PartialEq<Vec<u8>> for CheetahString {
858 #[inline]
859 fn eq(&self, other: &Vec<u8>) -> bool {
860 self.as_bytes() == other.as_slice()
861 }
862}
863
864impl<'a> PartialEq<&'a str> for CheetahString {
865 #[inline]
866 fn eq(&self, other: &&'a str) -> bool {
867 self.as_str() == *other
868 }
869}
870
871impl PartialEq<CheetahString> for str {
872 #[inline]
873 fn eq(&self, other: &CheetahString) -> bool {
874 self == other.as_str()
875 }
876}
877
878impl PartialEq<CheetahString> for String {
879 #[inline]
880 fn eq(&self, other: &CheetahString) -> bool {
881 self.as_str() == other.as_str()
882 }
883}
884
885impl PartialEq<CheetahString> for &str {
886 #[inline]
887 fn eq(&self, other: &CheetahString) -> bool {
888 *self == other.as_str()
889 }
890}
891
892impl Eq for CheetahString {}
893
894impl PartialOrd for CheetahString {
895 #[inline]
896 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
897 Some(self.cmp(other))
898 }
899}
900
901impl Ord for CheetahString {
902 #[inline]
903 fn cmp(&self, other: &Self) -> Ordering {
904 self.as_str().cmp(other.as_str())
905 }
906}
907
908impl Hash for CheetahString {
909 #[inline]
910 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
911 self.as_str().hash(state);
912 }
913}
914
915impl Display for CheetahString {
916 #[inline]
917 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
918 self.as_str().fmt(f)
919 }
920}
921
922impl std::fmt::Debug for CheetahString {
923 #[inline]
924 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
925 fmt::Debug::fmt(self.as_str(), f)
926 }
927}
928
929impl Borrow<str> for CheetahString {
930 #[inline]
931 fn borrow(&self) -> &str {
932 self.as_str()
933 }
934}
935
936// Add trait implementations for string concatenation
937
938impl std::ops::Add<&str> for CheetahString {
939 type Output = CheetahString;
940
941 /// Concatenates a `CheetahString` with a string slice.
942 ///
943 /// # Examples
944 ///
945 /// ```
946 /// use cheetah_string::CheetahString;
947 ///
948 /// let s = CheetahString::from("Hello");
949 /// let result = s + " World";
950 /// assert_eq!(result, "Hello World");
951 /// ```
952 #[inline]
953 fn add(self, rhs: &str) -> Self::Output {
954 let total_len = self.len() + rhs.len();
955
956 // Fast path: result fits in inline storage
957 if total_len <= INLINE_CAPACITY {
958 let mut data = [0u8; INLINE_CAPACITY];
959 let self_bytes = self.as_bytes();
960 data[..self_bytes.len()].copy_from_slice(self_bytes);
961 data[self_bytes.len()..total_len].copy_from_slice(rhs.as_bytes());
962 return CheetahString {
963 inner: InnerString::Inline {
964 len: total_len as u8,
965 data,
966 },
967 };
968 }
969
970 // Slow path: allocate for long result
971 let mut result = String::with_capacity(total_len);
972 result.push_str(self.as_str());
973 result.push_str(rhs);
974 CheetahString::from_string(result)
975 }
976}
977
978impl std::ops::Add<&CheetahString> for CheetahString {
979 type Output = CheetahString;
980
981 /// Concatenates two `CheetahString` values.
982 ///
983 /// # Examples
984 ///
985 /// ```
986 /// use cheetah_string::CheetahString;
987 ///
988 /// let s1 = CheetahString::from("Hello");
989 /// let s2 = CheetahString::from(" World");
990 /// let result = s1 + &s2;
991 /// assert_eq!(result, "Hello World");
992 /// ```
993 #[inline]
994 fn add(self, rhs: &CheetahString) -> Self::Output {
995 let total_len = self.len() + rhs.len();
996
997 // Fast path: result fits in inline storage
998 if total_len <= INLINE_CAPACITY {
999 let mut data = [0u8; INLINE_CAPACITY];
1000 let self_bytes = self.as_bytes();
1001 data[..self_bytes.len()].copy_from_slice(self_bytes);
1002 data[self_bytes.len()..total_len].copy_from_slice(rhs.as_bytes());
1003 return CheetahString {
1004 inner: InnerString::Inline {
1005 len: total_len as u8,
1006 data,
1007 },
1008 };
1009 }
1010
1011 // Slow path: allocate for long result
1012 let mut result = String::with_capacity(total_len);
1013 result.push_str(self.as_str());
1014 result.push_str(rhs.as_str());
1015 CheetahString::from_string(result)
1016 }
1017}
1018
1019impl std::ops::Add<String> for CheetahString {
1020 type Output = CheetahString;
1021
1022 /// Concatenates a `CheetahString` with a `String`.
1023 ///
1024 /// # Examples
1025 ///
1026 /// ```
1027 /// use cheetah_string::CheetahString;
1028 ///
1029 /// let s = CheetahString::from("Hello");
1030 /// let result = s + String::from(" World");
1031 /// assert_eq!(result, "Hello World");
1032 /// ```
1033 #[inline]
1034 fn add(self, rhs: String) -> Self::Output {
1035 let total_len = self.len() + rhs.len();
1036
1037 // Fast path: result fits in inline storage
1038 if total_len <= INLINE_CAPACITY {
1039 let mut data = [0u8; INLINE_CAPACITY];
1040 let self_bytes = self.as_bytes();
1041 data[..self_bytes.len()].copy_from_slice(self_bytes);
1042 data[self_bytes.len()..total_len].copy_from_slice(rhs.as_bytes());
1043 return CheetahString {
1044 inner: InnerString::Inline {
1045 len: total_len as u8,
1046 data,
1047 },
1048 };
1049 }
1050
1051 // Slow path: allocate for long result
1052 let mut result = String::with_capacity(total_len);
1053 result.push_str(self.as_str());
1054 result.push_str(&rhs);
1055 CheetahString::from_string(result)
1056 }
1057}
1058
1059impl std::ops::AddAssign<&str> for CheetahString {
1060 /// Appends a string slice to a `CheetahString`.
1061 ///
1062 /// # Examples
1063 ///
1064 /// ```
1065 /// use cheetah_string::CheetahString;
1066 ///
1067 /// let mut s = CheetahString::from("Hello");
1068 /// s += " World";
1069 /// assert_eq!(s, "Hello World");
1070 /// ```
1071 #[inline]
1072 fn add_assign(&mut self, rhs: &str) {
1073 let total_len = self.len() + rhs.len();
1074
1075 match &mut self.inner {
1076 // Fast path 1: Both self and result fit in inline storage
1077 InnerString::Inline { len, data } if total_len <= INLINE_CAPACITY => {
1078 // Mutate inline buffer directly
1079 data[*len as usize..total_len].copy_from_slice(rhs.as_bytes());
1080 *len = total_len as u8;
1081 return;
1082 }
1083 // Fast path 2: Self is unique Arc<String>, mutate in-place
1084 InnerString::ArcString(arc) if Arc::strong_count(arc) == 1 => {
1085 // SAFETY: strong_count == 1 guarantees exclusive access
1086 if let Some(s) = Arc::get_mut(arc) {
1087 s.push_str(rhs);
1088 return;
1089 }
1090 }
1091 _ => {}
1092 }
1093
1094 // Slow path: allocate new string
1095 let mut result = String::with_capacity(total_len);
1096 result.push_str(self.as_str());
1097 result.push_str(rhs);
1098 *self = CheetahString::from_string(result);
1099 }
1100}
1101
1102impl std::ops::AddAssign<&CheetahString> for CheetahString {
1103 /// Appends a `CheetahString` to another `CheetahString`.
1104 ///
1105 /// # Examples
1106 ///
1107 /// ```
1108 /// use cheetah_string::CheetahString;
1109 ///
1110 /// let mut s1 = CheetahString::from("Hello");
1111 /// let s2 = CheetahString::from(" World");
1112 /// s1 += &s2;
1113 /// assert_eq!(s1, "Hello World");
1114 /// ```
1115 #[inline]
1116 fn add_assign(&mut self, rhs: &CheetahString) {
1117 let total_len = self.len() + rhs.len();
1118
1119 match &mut self.inner {
1120 // Fast path 1: Both self and result fit in inline storage
1121 InnerString::Inline { len, data } if total_len <= INLINE_CAPACITY => {
1122 // Mutate inline buffer directly
1123 data[*len as usize..total_len].copy_from_slice(rhs.as_bytes());
1124 *len = total_len as u8;
1125 return;
1126 }
1127 // Fast path 2: Self is unique Arc<String>, mutate in-place
1128 InnerString::ArcString(arc) if Arc::strong_count(arc) == 1 => {
1129 // SAFETY: strong_count == 1 guarantees exclusive access
1130 if let Some(s) = Arc::get_mut(arc) {
1131 s.push_str(rhs.as_str());
1132 return;
1133 }
1134 }
1135 _ => {}
1136 }
1137
1138 // Slow path: allocate new string
1139 let mut result = String::with_capacity(total_len);
1140 result.push_str(self.as_str());
1141 result.push_str(rhs.as_str());
1142 *self = CheetahString::from_string(result);
1143 }
1144}
1145
1146/// Maximum capacity for inline string storage (23 bytes + 1 byte for length = 24 bytes total)
1147const INLINE_CAPACITY: usize = 23;
1148
1149/// The `InnerString` enum represents different types of string storage.
1150///
1151/// This enum uses Small String Optimization (SSO) to avoid heap allocations for short strings.
1152///
1153/// Variants:
1154///
1155/// * `Inline` - Inline storage for strings <= 23 bytes (zero heap allocations).
1156/// * `StaticStr(&'static str)` - A static string slice (zero heap allocations).
1157/// * `ArcStr(Arc<str>)` - A reference-counted string slice (single heap allocation, optimized).
1158/// * `ArcString(Arc<String>)` - A reference-counted string (for backwards compatibility).
1159/// * `ArcVecString(Arc<Vec<u8>>)` - A reference-counted byte vector.
1160/// * `Bytes(bytes::Bytes)` - A byte buffer (available when the "bytes" feature is enabled).
1161#[derive(Clone)]
1162pub(super) enum InnerString {
1163 /// Inline storage for short strings (up to 23 bytes).
1164 /// Stores the length and data directly without heap allocation.
1165 Inline {
1166 len: u8,
1167 data: [u8; INLINE_CAPACITY],
1168 },
1169 /// Static string slice with 'static lifetime.
1170 StaticStr(&'static str),
1171 /// Reference-counted string slice (single heap allocation).
1172 /// Preferred over ArcString for long strings created from owned data.
1173 ArcStr(Arc<str>),
1174 /// Reference-counted heap-allocated string.
1175 /// Kept for backwards compatibility and when Arc<String> is explicitly provided.
1176 ArcString(Arc<String>),
1177 /// Reference-counted heap-allocated byte vector.
1178 ArcVecString(Arc<Vec<u8>>),
1179 /// Bytes type integration (requires "bytes" feature).
1180 #[cfg(feature = "bytes")]
1181 Bytes(bytes::Bytes),
1182}