use alloc::borrow::Cow;
use alloc::string::{ParseError, String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::fmt::{self, Display};
use core::hash::{Hash, Hasher};
use core::ops::{Add, AddAssign, Deref};
use core::str::{self, FromStr, Utf8Error};
#[derive(Clone)]
#[repr(transparent)]
pub struct CheetahString {
pub(super) inner: InnerString,
}
impl Default for CheetahString {
fn default() -> Self {
CheetahString {
inner: InnerString::Inline {
len: 0,
data: [0; INLINE_CAPACITY],
},
}
}
}
impl From<String> for CheetahString {
#[inline]
fn from(s: String) -> Self {
CheetahString::from_string(s)
}
}
impl From<Arc<String>> for CheetahString {
#[inline]
fn from(s: Arc<String>) -> Self {
CheetahString::from_arc_string(s)
}
}
impl<'a> From<&'a str> for CheetahString {
#[inline]
fn from(s: &'a str) -> Self {
CheetahString::from_slice(s)
}
}
impl<'a> TryFrom<&'a [u8]> for CheetahString {
type Error = Utf8Error;
#[inline]
fn try_from(b: &'a [u8]) -> Result<Self, Self::Error> {
CheetahString::try_from_bytes(b)
}
}
impl FromStr for CheetahString {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(CheetahString::from_slice(s))
}
}
impl TryFrom<Vec<u8>> for CheetahString {
type Error = Utf8Error;
#[inline]
fn try_from(v: Vec<u8>) -> Result<Self, Self::Error> {
CheetahString::try_from_vec(v)
}
}
impl From<Cow<'static, str>> for CheetahString {
#[inline]
fn from(cow: Cow<'static, str>) -> Self {
match cow {
Cow::Borrowed(s) => CheetahString::from_static_str(s),
Cow::Owned(s) => CheetahString::from_string(s),
}
}
}
impl From<Cow<'_, String>> for CheetahString {
#[inline]
fn from(cow: Cow<'_, String>) -> Self {
match cow {
Cow::Borrowed(s) => CheetahString::from_slice(s),
Cow::Owned(s) => CheetahString::from_string(s),
}
}
}
impl From<char> for CheetahString {
#[inline]
fn from(c: char) -> Self {
CheetahString::from_string(c.to_string())
}
}
impl<'a> FromIterator<&'a char> for CheetahString {
#[inline]
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> CheetahString {
let mut buf = String::new();
buf.extend(iter);
CheetahString::from_string(buf)
}
}
impl<'a> FromIterator<&'a str> for CheetahString {
fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> CheetahString {
let mut buf = String::new();
buf.extend(iter);
CheetahString::from_string(buf)
}
}
impl FromIterator<String> for CheetahString {
#[inline]
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
let mut buf = String::new();
buf.extend(iter);
CheetahString::from_string(buf)
}
}
impl<'a> FromIterator<&'a String> for CheetahString {
#[inline]
fn from_iter<T: IntoIterator<Item = &'a String>>(iter: T) -> Self {
let mut buf = String::new();
buf.extend(iter.into_iter().map(|s| s.as_str()));
CheetahString::from_string(buf)
}
}
#[cfg(feature = "bytes")]
impl TryFrom<bytes::Bytes> for CheetahString {
type Error = Utf8Error;
#[inline]
fn try_from(b: bytes::Bytes) -> Result<Self, Self::Error> {
CheetahString::try_from_bytes_buf(b)
}
}
impl From<&CheetahString> for CheetahString {
#[inline]
fn from(s: &CheetahString) -> Self {
s.clone()
}
}
impl From<CheetahString> for String {
#[inline]
fn from(s: CheetahString) -> Self {
match s {
CheetahString {
inner: InnerString::Inline { len, data },
} => {
unsafe { String::from_utf8_unchecked(data[..len as usize].to_vec()) }
}
CheetahString {
inner: InnerString::Static(s),
} => s.to_string(),
CheetahString {
inner: InnerString::Shared(s),
} => s.to_string(),
CheetahString {
inner: InnerString::Owned(s),
} => s,
}
}
}
impl Deref for CheetahString {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl AsRef<str> for CheetahString {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for CheetahString {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<CheetahString> for CheetahString {
#[inline]
fn as_ref(&self) -> &CheetahString {
self
}
}
impl From<&String> for CheetahString {
#[inline]
fn from(s: &String) -> Self {
CheetahString::from_slice(s)
}
}
impl CheetahString {
#[inline]
pub const fn empty() -> Self {
CheetahString {
inner: InnerString::Inline {
len: 0,
data: [0; INLINE_CAPACITY],
},
}
}
#[inline]
pub fn new() -> Self {
CheetahString::default()
}
#[inline]
pub const fn from_static_str(s: &'static str) -> Self {
CheetahString {
inner: InnerString::Static(s),
}
}
#[deprecated(
since = "1.1.0",
note = "use try_from_vec for checked construction or from_utf8_unchecked_vec for an explicit unsafe constructor"
)]
pub fn from_vec(s: Vec<u8>) -> Self {
CheetahString::try_from_vec(s).expect(
"CheetahString::from_vec requires valid UTF-8; use try_from_vec for fallible construction",
)
}
#[inline]
pub unsafe fn from_utf8_unchecked_vec(s: Vec<u8>) -> Self {
CheetahString::from_validated_vec_unchecked(s)
}
#[inline]
fn from_validated_vec_unchecked(s: Vec<u8>) -> Self {
if s.len() <= INLINE_CAPACITY {
let mut data = [0u8; INLINE_CAPACITY];
data[..s.len()].copy_from_slice(&s);
CheetahString {
inner: InnerString::Inline {
len: s.len() as u8,
data,
},
}
} else {
CheetahString::from_builder_string(unsafe { String::from_utf8_unchecked(s) })
}
}
pub fn try_from_vec(v: Vec<u8>) -> Result<Self, Utf8Error> {
str::from_utf8(&v)?;
Ok(CheetahString::from_validated_vec_unchecked(v))
}
pub fn try_from_bytes(b: &[u8]) -> Result<Self, Utf8Error> {
let s = str::from_utf8(b)?;
Ok(CheetahString::from_slice(s))
}
#[inline]
pub unsafe fn from_utf8_unchecked_bytes(b: &[u8]) -> Self {
CheetahString::from_slice(unsafe { str::from_utf8_unchecked(b) })
}
#[inline]
pub fn try_from_arc_vec(s: Arc<Vec<u8>>) -> Result<Self, Utf8Error> {
match Arc::try_unwrap(s) {
Ok(v) => CheetahString::try_from_vec(v),
Err(s) => {
let s = str::from_utf8(s.as_slice())?;
Ok(CheetahString::from_slice(s))
}
}
}
#[deprecated(
since = "1.1.0",
note = "use try_from_arc_vec for checked construction or from_utf8_unchecked_arc_vec for an explicit unsafe constructor"
)]
#[inline]
pub fn from_arc_vec(s: Arc<Vec<u8>>) -> Self {
CheetahString::try_from_arc_vec(s).expect(
"CheetahString::from_arc_vec requires valid UTF-8; use try_from_arc_vec for fallible construction",
)
}
#[inline]
pub unsafe fn from_utf8_unchecked_arc_vec(s: Arc<Vec<u8>>) -> Self {
CheetahString::from_validated_arc_vec_unchecked(s)
}
#[inline]
fn from_validated_arc_vec_unchecked(s: Arc<Vec<u8>>) -> Self {
match Arc::try_unwrap(s) {
Ok(v) => CheetahString::from_validated_vec_unchecked(v),
Err(s) => {
unsafe { CheetahString::from_utf8_unchecked_bytes(s.as_slice()) }
}
}
}
#[inline]
pub fn from_slice(s: &str) -> Self {
if s.len() <= INLINE_CAPACITY {
let mut data = [0u8; INLINE_CAPACITY];
data[..s.len()].copy_from_slice(s.as_bytes());
CheetahString {
inner: InnerString::Inline {
len: s.len() as u8,
data,
},
}
} else {
let arc_str: Arc<str> = Arc::from(s);
CheetahString {
inner: InnerString::Shared(arc_str),
}
}
}
#[inline]
pub fn from_string(s: String) -> Self {
CheetahString::from_string_shared(s)
}
#[inline]
pub fn from_string_owned(s: String) -> Self {
CheetahString::from_builder_string(s)
}
#[inline]
pub fn from_string_shared(s: String) -> Self {
if s.len() <= INLINE_CAPACITY {
let mut data = [0u8; INLINE_CAPACITY];
data[..s.len()].copy_from_slice(s.as_bytes());
CheetahString {
inner: InnerString::Inline {
len: s.len() as u8,
data,
},
}
} else {
let arc_str: Arc<str> = s.into_boxed_str().into();
CheetahString {
inner: InnerString::Shared(arc_str),
}
}
}
#[inline]
fn from_builder_string(s: String) -> Self {
if s.len() <= INLINE_CAPACITY && s.capacity() <= INLINE_CAPACITY {
let mut data = [0u8; INLINE_CAPACITY];
data[..s.len()].copy_from_slice(s.as_bytes());
CheetahString {
inner: InnerString::Inline {
len: s.len() as u8,
data,
},
}
} else {
CheetahString {
inner: InnerString::Owned(s),
}
}
}
#[inline]
pub fn from_arc_string(s: Arc<String>) -> Self {
match Arc::try_unwrap(s) {
Ok(s) => CheetahString::from_builder_string(s),
Err(s) => CheetahString::from_slice(s.as_str()),
}
}
#[inline]
#[cfg(feature = "bytes")]
#[deprecated(
since = "1.1.0",
note = "use try_from_bytes_buf for checked construction or from_utf8_unchecked_bytes_buf for an explicit unsafe constructor"
)]
pub fn from_bytes(b: bytes::Bytes) -> Self {
CheetahString::try_from_bytes_buf(b).expect(
"CheetahString::from_bytes requires valid UTF-8; use try_from_bytes_buf for fallible construction",
)
}
#[inline]
#[cfg(feature = "bytes")]
pub fn try_from_bytes_buf(b: bytes::Bytes) -> Result<Self, Utf8Error> {
str::from_utf8(b.as_ref())?;
Ok(CheetahString::from_validated_bytes_unchecked(b))
}
#[inline]
#[cfg(feature = "bytes")]
pub unsafe fn from_utf8_unchecked_bytes_buf(b: bytes::Bytes) -> Self {
CheetahString::from_validated_bytes_unchecked(b)
}
#[inline]
#[cfg(feature = "bytes")]
fn from_validated_bytes_unchecked(b: bytes::Bytes) -> Self {
unsafe { CheetahString::from_utf8_unchecked_bytes(b.as_ref()) }
}
#[inline]
pub fn as_str(&self) -> &str {
match &self.inner {
InnerString::Inline { len, data } => {
unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
}
InnerString::Static(s) => s,
InnerString::Shared(s) => s.as_ref(),
InnerString::Owned(s) => s.as_str(),
}
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
match &self.inner {
InnerString::Inline { len, data } => &data[..*len as usize],
InnerString::Static(s) => s.as_bytes(),
InnerString::Shared(s) => s.as_bytes(),
InnerString::Owned(s) => s.as_bytes(),
}
}
#[inline]
pub fn len(&self) -> usize {
match &self.inner {
InnerString::Inline { len, .. } => *len as usize,
InnerString::Static(s) => s.len(),
InnerString::Shared(s) => s.len(),
InnerString::Owned(s) => s.len(),
}
}
#[inline]
pub fn is_empty(&self) -> bool {
match &self.inner {
InnerString::Inline { len, .. } => *len == 0,
InnerString::Static(s) => s.is_empty(),
InnerString::Shared(s) => s.is_empty(),
InnerString::Owned(s) => s.is_empty(),
}
}
#[inline]
pub fn starts_with<P: StrPattern>(&self, pat: P) -> bool {
match pat.as_str_pattern() {
StrPatternImpl::Char(c) => self.as_str().starts_with(c),
StrPatternImpl::Str(s) => {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
if s.len() >= crate::simd::SIMD_THRESHOLD {
return crate::simd::starts_with_bytes(self.as_bytes(), s.as_bytes());
}
}
self.as_str().starts_with(s)
}
}
}
#[inline]
pub fn starts_with_char(&self, pat: char) -> bool {
self.as_str().starts_with(pat)
}
#[inline]
pub fn ends_with<P: StrPattern>(&self, pat: P) -> bool {
match pat.as_str_pattern() {
StrPatternImpl::Char(c) => self.as_str().ends_with(c),
StrPatternImpl::Str(s) => {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
if s.len() >= crate::simd::SIMD_THRESHOLD {
return crate::simd::ends_with_bytes(self.as_bytes(), s.as_bytes());
}
}
self.as_str().ends_with(s)
}
}
}
#[inline]
pub fn ends_with_char(&self, pat: char) -> bool {
self.as_str().ends_with(pat)
}
#[inline]
pub fn contains<P: StrPattern>(&self, pat: P) -> bool {
match pat.as_str_pattern() {
StrPatternImpl::Char(c) => self.as_str().contains(c),
StrPatternImpl::Str(s) => {
crate::search::find_bytes(self.as_bytes(), s.as_bytes()).is_some()
}
}
}
#[inline]
pub fn contains_char(&self, pat: char) -> bool {
self.as_str().contains(pat)
}
#[inline]
pub fn find<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
let pat = pat.as_ref();
crate::search::find_bytes(self.as_bytes(), pat.as_bytes())
}
#[inline]
pub fn rfind<P: AsRef<str>>(&self, pat: P) -> Option<usize> {
crate::search::rfind_bytes(self.as_bytes(), pat.as_ref().as_bytes())
}
#[inline]
pub fn trim(&self) -> &str {
self.as_str().trim()
}
#[inline]
pub fn trim_start(&self) -> &str {
self.as_str().trim_start()
}
#[inline]
pub fn trim_end(&self) -> &str {
self.as_str().trim_end()
}
#[inline]
pub fn split<'a, P>(&'a self, pat: P) -> SplitWrapper<'a>
where
P: SplitPattern<'a>,
{
pat.split_str(self.as_str())
}
#[inline]
pub fn lines(&self) -> impl Iterator<Item = &str> {
self.as_str().lines()
}
#[inline]
pub fn chars(&self) -> str::Chars<'_> {
self.as_str().chars()
}
#[inline]
pub fn to_uppercase(&self) -> CheetahString {
CheetahString::from_string(self.as_str().to_uppercase())
}
#[inline]
pub fn to_lowercase(&self) -> CheetahString {
CheetahString::from_string(self.as_str().to_lowercase())
}
#[inline]
pub fn replace<P: AsRef<str>>(&self, from: P, to: &str) -> CheetahString {
CheetahString::from_string(self.as_str().replace(from.as_ref(), to))
}
#[inline]
pub fn replacen<P: AsRef<str>>(&self, from: P, to: &str, count: usize) -> CheetahString {
CheetahString::from_string(self.as_str().replacen(from.as_ref(), to, count))
}
#[inline]
pub fn substring(&self, start: usize, end: usize) -> CheetahString {
CheetahString::from_slice(&self.as_str()[start..end])
}
#[inline]
pub fn repeat(&self, n: usize) -> CheetahString {
CheetahString::from_string(self.as_str().repeat(n))
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
if capacity <= INLINE_CAPACITY {
CheetahString::empty()
} else {
CheetahString::from_builder_string(String::with_capacity(capacity))
}
}
#[inline]
fn push_str_internal(&mut self, string: &str) {
if string.is_empty() {
return;
}
match &mut self.inner {
InnerString::Inline { len, data } => {
let total_len = *len as usize + string.len();
if total_len <= INLINE_CAPACITY {
data[*len as usize..total_len].copy_from_slice(string.as_bytes());
*len = total_len as u8;
return;
}
}
InnerString::Owned(s) => {
s.push_str(string);
return;
}
_ => {}
}
let total_len = self.len() + string.len();
let mut result = String::with_capacity(total_len);
result.push_str(self.as_str());
result.push_str(string);
*self = CheetahString::from_builder_string(result);
}
#[inline]
pub fn push_str(&mut self, string: &str) {
self.push_str_internal(string);
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
if additional == 0 {
return;
}
match &mut self.inner {
InnerString::Inline { len, .. } if *len as usize + additional <= INLINE_CAPACITY => {
return;
}
InnerString::Inline { .. } => {}
InnerString::Owned(s) => {
s.reserve(additional);
return;
}
_ => {}
}
let new_len = self.len() + additional;
let mut s = String::with_capacity(new_len);
s.push_str(self.as_str());
*self = CheetahString::from_builder_string(s);
}
}
impl PartialEq for CheetahString {
#[inline]
fn eq(&self, other: &Self) -> bool {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
{
self.as_str() == other.as_str()
}
}
}
impl PartialEq<str> for CheetahString {
#[inline]
fn eq(&self, other: &str) -> bool {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
{
self.as_str() == other
}
}
}
impl PartialEq<String> for CheetahString {
#[inline]
fn eq(&self, other: &String) -> bool {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
crate::simd::eq_bytes(self.as_bytes(), other.as_bytes())
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
{
self.as_str() == other.as_str()
}
}
}
impl PartialEq<Vec<u8>> for CheetahString {
#[inline]
fn eq(&self, other: &Vec<u8>) -> bool {
self.as_bytes() == other.as_slice()
}
}
impl<'a> PartialEq<&'a str> for CheetahString {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<CheetahString> for str {
#[inline]
fn eq(&self, other: &CheetahString) -> bool {
self == other.as_str()
}
}
impl PartialEq<CheetahString> for String {
#[inline]
fn eq(&self, other: &CheetahString) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<CheetahString> for &str {
#[inline]
fn eq(&self, other: &CheetahString) -> bool {
*self == other.as_str()
}
}
impl Eq for CheetahString {}
impl PartialOrd for CheetahString {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CheetahString {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl Hash for CheetahString {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl Display for CheetahString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl fmt::Debug for CheetahString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl Borrow<str> for CheetahString {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl Add<&str> for CheetahString {
type Output = CheetahString;
#[inline]
fn add(mut self, rhs: &str) -> Self::Output {
self.push_str_internal(rhs);
self
}
}
impl Add<&CheetahString> for CheetahString {
type Output = CheetahString;
#[inline]
fn add(mut self, rhs: &CheetahString) -> Self::Output {
self.push_str_internal(rhs.as_str());
self
}
}
impl Add<String> for CheetahString {
type Output = CheetahString;
#[inline]
fn add(mut self, rhs: String) -> Self::Output {
if self.is_empty() {
return CheetahString::from_string_owned(rhs);
}
self.push_str_internal(&rhs);
self
}
}
impl AddAssign<&str> for CheetahString {
#[inline]
fn add_assign(&mut self, rhs: &str) {
self.push_str_internal(rhs);
}
}
impl AddAssign<&CheetahString> for CheetahString {
#[inline]
fn add_assign(&mut self, rhs: &CheetahString) {
self.push_str_internal(rhs.as_str());
}
}
const INLINE_CAPACITY: usize = 23;
#[derive(Clone)]
pub(super) enum InnerString {
Inline {
len: u8,
data: [u8; INLINE_CAPACITY],
},
Static(&'static str),
Shared(Arc<str>),
Owned(String),
}
mod private {
use alloc::string::String;
pub trait Sealed {}
impl Sealed for char {}
impl Sealed for &str {}
impl Sealed for &String {}
pub trait SplitSealed {}
impl SplitSealed for char {}
impl SplitSealed for &str {}
}
pub trait StrPattern: private::Sealed {
#[doc(hidden)]
fn as_str_pattern(&self) -> StrPatternImpl<'_>;
}
#[doc(hidden)]
pub enum StrPatternImpl<'a> {
Char(char),
Str(&'a str),
}
impl StrPattern for char {
#[inline]
fn as_str_pattern(&self) -> StrPatternImpl<'_> {
StrPatternImpl::Char(*self)
}
}
impl StrPattern for &str {
#[inline]
fn as_str_pattern(&self) -> StrPatternImpl<'_> {
StrPatternImpl::Str(self)
}
}
impl StrPattern for &String {
#[inline]
fn as_str_pattern(&self) -> StrPatternImpl<'_> {
StrPatternImpl::Str(self.as_str())
}
}
pub trait SplitPattern<'a>: private::SplitSealed {
#[doc(hidden)]
fn split_str(self, s: &'a str) -> SplitWrapper<'a>;
}
impl SplitPattern<'_> for char {
fn split_str(self, s: &str) -> SplitWrapper<'_> {
SplitWrapper::Char(s.split(self))
}
}
impl<'a> SplitPattern<'a> for &'a str {
fn split_str(self, s: &'a str) -> SplitWrapper<'a> {
let inner = match single_char_pattern(self) {
Some(ch) => SplitStrInner::Char(s.split(ch)),
None => SplitStrInner::Str(s.split(self)),
};
SplitWrapper::Str(SplitStr(inner))
}
}
pub struct SplitStr<'a>(SplitStrInner<'a>);
enum SplitStrInner<'a> {
Str(str::Split<'a, &'a str>),
Char(str::Split<'a, char>),
}
#[inline]
fn single_char_pattern(pattern: &str) -> Option<char> {
let mut chars = pattern.chars();
let ch = chars.next()?;
if chars.next().is_none() {
Some(ch)
} else {
None
}
}
impl<'a> Iterator for SplitStr<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
match &mut self.0 {
SplitStrInner::Str(iter) => iter.next(),
SplitStrInner::Char(iter) => iter.next(),
}
}
}
pub enum SplitWrapper<'a> {
#[doc(hidden)]
Char(str::Split<'a, char>),
#[doc(hidden)]
Str(SplitStr<'a>),
}
impl<'a> Iterator for SplitWrapper<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
match self {
SplitWrapper::Char(iter) => iter.next(),
SplitWrapper::Str(iter) => iter.next(),
}
}
}
impl<'a> DoubleEndedIterator for SplitWrapper<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
match self {
SplitWrapper::Char(iter) => iter.next_back(),
SplitWrapper::Str(_) => {
panic!("split with string pattern does not support reverse iteration")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{format, vec};
#[test]
fn with_capacity_above_inline_uses_heap_storage() {
let s = CheetahString::with_capacity(INLINE_CAPACITY + 8);
match &s.inner {
InnerString::Owned(inner) => {
assert!(inner.capacity() >= INLINE_CAPACITY + 8);
}
other => panic!(
"expected heap-backed storage from with_capacity, got {:?}",
core::mem::discriminant(other)
),
}
}
#[test]
fn push_str_promotes_builder_growth_to_owned_storage() {
let suffix = "a".repeat(INLINE_CAPACITY);
let expected = format!("hello{suffix}");
let mut s = CheetahString::from("hello");
s.push_str(&suffix);
match &s.inner {
InnerString::Owned(inner) => {
assert_eq!(inner.as_str(), expected.as_str());
assert!(inner.capacity() >= expected.len());
}
other => panic!(
"expected owned heap storage after builder growth, got {:?}",
core::mem::discriminant(other)
),
}
}
#[test]
fn long_borrowed_str_uses_shared_storage() {
let value = "a".repeat(INLINE_CAPACITY + 1);
let s = CheetahString::from_slice(&value);
match &s.inner {
InnerString::Shared(inner) => assert_eq!(inner.as_ref(), value.as_str()),
other => panic!(
"expected Shared for long borrowed input, got {:?}",
core::mem::discriminant(other)
),
}
}
#[test]
fn try_from_vec_short_input_uses_inline_storage() {
let s = CheetahString::try_from_vec(b"hello".to_vec()).expect("valid utf-8");
match &s.inner {
InnerString::Inline { len, data } => {
assert_eq!(*len as usize, 5);
assert_eq!(&data[..5], b"hello");
}
other => panic!(
"expected inline storage for short validated Vec<u8>, got {:?}",
core::mem::discriminant(other)
),
}
}
#[test]
fn long_vec_conversion_uses_owned_storage() {
let value = "a".repeat(INLINE_CAPACITY + 1).into_bytes();
let s = CheetahString::try_from_vec(value).expect("valid utf-8");
match &s.inner {
InnerString::Owned(inner) => {
assert_eq!(inner.len(), INLINE_CAPACITY + 1);
assert_eq!(inner.as_bytes(), vec![b'a'; INLINE_CAPACITY + 1].as_slice());
}
other => panic!(
"expected Owned for long Vec<u8> conversion, got {:?}",
core::mem::discriminant(other)
),
}
}
}