use std::{
borrow::{Borrow, BorrowMut, Cow},
boxed::Box,
cmp::Ordering,
convert::Infallible,
fmt,
hash::{self, Hash, Hasher},
iter::FromIterator,
mem,
ops::{Add, Deref, DerefMut, Index},
ptr, str,
string::String,
sync::Arc,
};
use bytes::{Bytes, BytesMut};
use bytes_utils::{Str as BytesStr, StrMut as BytesStrMut, string::StorageMut};
#[derive(Default, Clone)]
#[repr(transparent)]
pub struct Str(Repr<BytesStr>);
pub type Utf8Error = bytes_utils::string::Utf8Error<Bytes>;
pub type Utf8ErrorMut = bytes_utils::string::Utf8Error<BytesMut>;
impl Str {
#[inline]
pub unsafe fn from_utf8_unchecked_owned(u: impl Into<Bytes>) -> Self {
Self(Repr::Heap(unsafe { BytesStr::from_inner_unchecked(u.into()) }))
}
#[inline]
pub fn promote(&mut self) -> &mut BytesStr {
if let Repr::Inline(buf) = &mut self.0 {
self.0 = Repr::Heap(buf.as_str().into());
}
let Repr::Heap(data) = &mut self.0 else {
unreachable!();
};
data
}
#[inline]
pub unsafe fn from_utf8_unchecked(u: &[u8]) -> Self {
Self::new(unsafe { str::from_utf8_unchecked(u) })
}
#[inline]
pub fn from_utf8_owned(u: impl Into<Bytes>) -> Result<Self, Utf8Error> {
Ok(Self(Repr::Heap(BytesStr::from_inner(u.into())?)))
}
#[inline]
pub fn from_utf8(u: &[u8]) -> Result<Self, str::Utf8Error> {
Ok(Self::new(str::from_utf8(u)?))
}
#[inline]
pub fn from_utf8_lossy(u: &[u8]) -> Self {
match String::from_utf8_lossy(u) {
Cow::Borrowed(text) => Self::new(text),
Cow::Owned(text) => Self::from(text),
}
}
#[inline]
pub fn new_inline(text: &str) -> Self {
Self(Repr::new_inline(text).expect("len <= INLINE_CAP"))
}
#[inline(always)]
pub const fn new_static(text: &'static str) -> Self {
Self(Repr::Heap(BytesStr::from_static(text)))
}
#[inline(always)]
pub fn new(text: impl AsRef<str>) -> Self {
Self(Repr::copy_from_str(text.as_ref()))
}
#[inline(always)]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline(always)]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline(always)]
pub const fn is_spilled(&self) -> bool {
matches!(self.0, Repr::Heap(..))
}
#[inline(always)]
pub fn is_unique(&self) -> bool {
match &self.0 {
Repr::Heap(data) => data.inner().is_unique(),
Repr::Inline(_) => true,
}
}
#[inline]
pub fn strip_prefix(&self, prefix: &str) -> Option<Self> {
let s = self.as_str();
s.strip_prefix(prefix).map(|r| self.slice_ref(r))
}
#[inline]
pub fn strip_suffix(&self, suffix: &str) -> Option<Self> {
let s = self.as_str();
s.strip_suffix(suffix).map(|r| self.slice_ref(r))
}
#[inline]
pub fn slice<R>(&self, range: R) -> Self
where
str: Index<R, Output = str>,
{
match &self.0 {
Repr::Heap(data) => Self(Repr::Heap(data.slice(range))),
_ => Self::new_inline(&self[range]),
}
}
#[inline]
pub fn slice_ref(&self, subset: &str) -> Self {
match &self.0 {
Repr::Heap(data) => Self(Repr::Heap(data.slice_ref(subset))),
_ => Self::new_inline(subset),
}
}
#[inline]
pub fn split_at(&self, at: usize) -> (Self, Self) {
match &self.0 {
Repr::Heap(data) => {
let (left, right) = data.clone().split_at_bytes(at);
(Self(Repr::Heap(left)), Self(Repr::Heap(right)))
},
Repr::Inline(buf) => {
let (left, right) = buf.split_at(at);
(Self::new_inline(left), Self::new_inline(right))
},
}
}
#[inline]
pub fn trim_start(&self) -> Self {
let trimmed = self.as_str().trim_start();
self.slice_ref(trimmed)
}
#[inline]
pub fn trim_end(&self) -> Self {
let trimmed = self.as_str().trim_end();
self.slice_ref(trimmed)
}
#[inline]
pub fn trim(&self) -> Self {
let trimmed = self.as_str().trim();
self.slice_ref(trimmed)
}
#[inline]
pub fn truncate(&mut self, len: usize) {
match &mut self.0 {
Repr::Inline(buf) => {
buf.truncate(len);
},
Repr::Heap(heap) => {
assert!(heap.is_char_boundary(len), "Index is not on a char boundary");
unsafe {
let mut bytes = mem::take(heap).into_inner();
bytes.truncate(len);
*heap = BytesStr::from_inner_unchecked(bytes);
}
},
}
}
pub fn split<'s>(
&'s self,
separator: &'s str,
) -> impl Clone + std::iter::FusedIterator<Item = Self> + 's {
self
.as_str()
.split(separator)
.map(move |s| self.slice_ref(s))
}
pub fn into_ascii_lowercase(self) -> Self {
let mut buf = StrMut::from(self);
buf.make_ascii_lowercase();
buf.freeze()
}
pub fn into_ascii_uppercase(self) -> Self {
let mut buf = StrMut::from(self);
buf.make_ascii_uppercase();
buf.freeze()
}
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
#[inline]
pub fn try_into_mut(self) -> Result<StrMut, Self> {
match self.0 {
Repr::Heap(data) => match data.into_inner().try_into_mut() {
Ok(data) => Ok(StrMut(Repr::Heap(unsafe { BytesStrMut::from_inner_unchecked(data) }))),
Err(e) => Err(Self(Repr::Heap(unsafe { BytesStr::from_inner_unchecked(e) }))),
},
Repr::Inline(buf) => Ok(StrMut(Repr::Inline(buf))),
}
}
}
pub trait StrExt {
fn to_ascii_lowercase_str(&self) -> Str;
fn to_ascii_uppercase_str(&self) -> Str;
}
impl StrExt for str {
fn to_ascii_lowercase_str(&self) -> Str {
let mut s = StrMut::new(self);
s.make_ascii_lowercase();
s.freeze()
}
fn to_ascii_uppercase_str(&self) -> Str {
let mut s = StrMut::new(self);
s.make_ascii_uppercase();
s.freeze()
}
}
impl Eq for Str {}
impl PartialEq<Self> for Str {
fn eq(&self, other: &Self) -> bool {
self.0.ptr_eq(&other.0) || self.as_str() == other.as_str()
}
}
impl PartialEq<str> for Str {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<Str> for str {
#[inline(always)]
fn eq(&self, other: &Str) -> bool {
other == self
}
}
impl<'a> PartialEq<&'a str> for Str {
#[inline(always)]
fn eq(&self, other: &&'a str) -> bool {
self == *other
}
}
impl PartialEq<Str> for &str {
#[inline(always)]
fn eq(&self, other: &Str) -> bool {
*self == other
}
}
impl PartialEq<String> for Str {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other
}
}
impl PartialEq<Str> for String {
#[inline(always)]
fn eq(&self, other: &Str) -> bool {
other == self
}
}
impl<'a> PartialEq<&'a String> for Str {
#[inline(always)]
fn eq(&self, other: &&'a String) -> bool {
self == *other
}
}
impl PartialEq<Str> for &String {
#[inline(always)]
fn eq(&self, other: &Str) -> bool {
*self == other
}
}
impl Ord for Str {
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl PartialOrd for Str {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<str> for Str {
#[inline(always)]
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
self.as_str().partial_cmp(other)
}
}
impl PartialOrd<Str> for str {
#[inline(always)]
fn partial_cmp(&self, other: &Str) -> Option<Ordering> {
self.partial_cmp(other.as_str())
}
}
impl<'a> PartialOrd<&'a str> for Str {
#[inline(always)]
fn partial_cmp(&self, other: &&'a str) -> Option<Ordering> {
self.partial_cmp(*other)
}
}
impl PartialOrd<Str> for &str {
#[inline(always)]
fn partial_cmp(&self, other: &Str) -> Option<Ordering> {
(*self).partial_cmp(other)
}
}
impl hash::Hash for Str {
fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
self.as_str().hash(hasher);
}
}
impl fmt::Debug for Str {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl fmt::Display for Str {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl fmt::Debug for StrMut {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl fmt::Display for StrMut {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl Eq for StrMut {}
impl PartialEq<Self> for StrMut {
fn eq(&self, other: &Self) -> bool {
self.0.ptr_eq(&other.0) || self.as_str() == other.as_str()
}
}
impl PartialEq<str> for StrMut {
#[inline(always)]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<StrMut> for str {
#[inline(always)]
fn eq(&self, other: &StrMut) -> bool {
other == self
}
}
impl<'a> PartialEq<&'a str> for StrMut {
#[inline(always)]
fn eq(&self, other: &&'a str) -> bool {
self == *other
}
}
impl PartialEq<StrMut> for &str {
#[inline(always)]
fn eq(&self, other: &StrMut) -> bool {
*self == other
}
}
impl PartialEq<String> for StrMut {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other
}
}
impl PartialEq<StrMut> for String {
#[inline(always)]
fn eq(&self, other: &StrMut) -> bool {
other == self
}
}
impl<'a> PartialEq<&'a String> for StrMut {
#[inline(always)]
fn eq(&self, other: &&'a String) -> bool {
self == *other
}
}
impl PartialEq<StrMut> for &String {
#[inline(always)]
fn eq(&self, other: &StrMut) -> bool {
*self == other
}
}
impl Ord for StrMut {
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl PartialOrd for StrMut {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<str> for StrMut {
#[inline(always)]
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
self.as_str().partial_cmp(other)
}
}
impl PartialOrd<StrMut> for str {
#[inline(always)]
fn partial_cmp(&self, other: &StrMut) -> Option<Ordering> {
self.partial_cmp(other.as_str())
}
}
impl<'a> PartialOrd<&'a str> for StrMut {
#[inline(always)]
fn partial_cmp(&self, other: &&'a str) -> Option<Ordering> {
self.partial_cmp(*other)
}
}
impl PartialOrd<StrMut> for &str {
#[inline(always)]
fn partial_cmp(&self, other: &StrMut) -> Option<Ordering> {
(*self).partial_cmp(other)
}
}
impl hash::Hash for StrMut {
fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
self.as_str().hash(hasher);
}
}
impl AsRef<str> for Str {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for Str {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl AsRef<std::ffi::OsStr> for Str {
#[inline(always)]
fn as_ref(&self) -> &std::ffi::OsStr {
AsRef::<std::ffi::OsStr>::as_ref(self.as_str())
}
}
impl AsRef<std::path::Path> for Str {
#[inline(always)]
fn as_ref(&self) -> &std::path::Path {
AsRef::<std::path::Path>::as_ref(self.as_str())
}
}
impl Borrow<str> for Str {
#[inline(always)]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl Deref for Str {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for StrMut {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsMut<str> for StrMut {
#[inline(always)]
fn as_mut(&mut self) -> &mut str {
self.as_str_mut()
}
}
impl AsRef<[u8]> for StrMut {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl AsRef<std::ffi::OsStr> for StrMut {
#[inline(always)]
fn as_ref(&self) -> &std::ffi::OsStr {
AsRef::<std::ffi::OsStr>::as_ref(self.as_str())
}
}
impl AsRef<std::path::Path> for StrMut {
#[inline(always)]
fn as_ref(&self) -> &std::path::Path {
AsRef::<std::path::Path>::as_ref(self.as_str())
}
}
impl Borrow<str> for StrMut {
#[inline(always)]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl BorrowMut<str> for StrMut {
#[inline(always)]
fn borrow_mut(&mut self) -> &mut str {
self.as_str_mut()
}
}
impl Deref for StrMut {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
self.as_str()
}
}
impl DerefMut for StrMut {
#[inline(always)]
fn deref_mut(&mut self) -> &mut str {
self.as_str_mut()
}
}
impl Add<&str> for Str {
type Output = Self;
#[inline]
fn add(self, rhs: &str) -> Self::Output {
match self.try_into_mut() {
Ok(mut lhs) => {
lhs.push_str(rhs);
lhs.freeze()
},
Err(this) => {
let mut lhs = StrMut::with_capacity(this.len() + rhs.len());
lhs.push_str(&this);
lhs.push_str(rhs);
lhs.freeze()
},
}
}
}
impl Add<Str> for &str {
type Output = Str;
#[inline]
fn add(self, rhs: Str) -> Self::Output {
match rhs.try_into_mut() {
Ok(mut rhs) => {
rhs.insert(0, self);
rhs.freeze()
},
Err(rhs) => {
let mut result = StrMut::with_capacity(self.len() + rhs.len());
result.push_str(self);
result.push_str(rhs.as_str());
result.freeze()
},
}
}
}
impl Add<&str> for &Str {
type Output = Str;
#[inline]
fn add(self, rhs: &str) -> Self::Output {
let mut result = StrMut::with_capacity(self.len() + rhs.len());
result.push_str(self);
result.push_str(rhs);
result.freeze()
}
}
impl Add<&Str> for &str {
type Output = Str;
#[inline]
fn add(self, rhs: &Str) -> Self::Output {
let mut result = StrMut::with_capacity(self.len() + rhs.len());
result.push_str(self);
result.push_str(rhs.as_str());
result.freeze()
}
}
impl Add<&str> for StrMut {
type Output = Self;
#[inline]
fn add(mut self, rhs: &str) -> Self::Output {
self.push_str(rhs);
self
}
}
impl Add<StrMut> for &str {
type Output = StrMut;
#[inline]
fn add(self, mut rhs: StrMut) -> Self::Output {
rhs.insert(0, self);
rhs
}
}
impl Add<&str> for &StrMut {
type Output = StrMut;
#[inline]
fn add(self, rhs: &str) -> Self::Output {
let mut result = StrMut::with_capacity(self.len() + rhs.len());
result.push_str(self.as_str());
result.push_str(rhs);
result
}
}
impl Add<&StrMut> for &str {
type Output = StrMut;
#[inline]
fn add(self, rhs: &StrMut) -> Self::Output {
let mut result = StrMut::with_capacity(self.len() + rhs.len());
result.push_str(self);
result.push_str(rhs.as_str());
result
}
}
impl Add<Self> for Str {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self::Output {
match self.try_into_mut() {
Ok(mut lhs) => {
lhs.push_str(&rhs);
lhs.freeze()
},
Err(lhs) => match rhs.try_into_mut() {
Ok(mut rhs) => {
rhs.insert(0, &lhs);
rhs.freeze()
},
Err(rhs) => {
let mut result = StrMut::with_capacity(lhs.len() + rhs.len());
result.push_str(&lhs);
result.push_str(&rhs);
result.freeze()
},
},
}
}
}
impl Add<&Self> for Str {
type Output = Self;
#[inline]
fn add(self, rhs: &Self) -> Self::Output {
match self.try_into_mut() {
Ok(mut lhs) => {
lhs.push_str(rhs);
lhs.freeze()
},
Err(lhs) => {
let mut result = StrMut::with_capacity(lhs.len() + rhs.len());
result.push_str(&lhs);
result.push_str(rhs);
result.freeze()
},
}
}
}
impl Add<Str> for &Str {
type Output = Str;
#[inline]
fn add(self, rhs: Str) -> Self::Output {
match rhs.try_into_mut() {
Ok(mut rhs) => {
rhs.insert(0, self);
rhs.freeze()
},
Err(rhs) => {
let mut result = StrMut::with_capacity(self.len() + rhs.len());
result.push_str(self);
result.push_str(&rhs);
result.freeze()
},
}
}
}
impl Add<&Str> for &Str {
type Output = Str;
#[inline]
fn add(self, rhs: &Str) -> Self::Output {
let mut result = StrMut::with_capacity(self.len() + rhs.len());
result.push_str(self);
result.push_str(rhs);
result.freeze()
}
}
impl Add<Self> for StrMut {
type Output = Self;
#[inline]
fn add(mut self, rhs: Self) -> Self::Output {
self.push_str(&rhs);
self
}
}
impl Add<&Self> for StrMut {
type Output = Self;
#[inline]
fn add(mut self, rhs: &Self) -> Self::Output {
self.push_str(rhs.as_str());
self
}
}
impl Add<StrMut> for &StrMut {
type Output = StrMut;
#[inline]
fn add(self, mut rhs: StrMut) -> Self::Output {
rhs.insert(0, self.as_str());
rhs
}
}
impl Add<&StrMut> for &StrMut {
type Output = StrMut;
#[inline]
fn add(self, rhs: &StrMut) -> Self::Output {
let mut result = StrMut::with_capacity(self.len() + rhs.len());
result.push_str(self.as_str());
result.push_str(rhs.as_str());
result
}
}
const INLINE_CAP: usize = 23;
#[derive(Debug)]
enum Repr<H> {
Inline(heapless::String<INLINE_CAP, u8>),
Heap(H),
}
impl<H: Clone> Clone for Repr<H> {
#[inline]
fn clone(&self) -> Self {
match self {
Self::Heap(data) => Self::Heap(data.clone()),
_ => unsafe { ptr::read(self as *const Self) },
}
}
}
impl<H> Default for Repr<H> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<H> Repr<H> {
#[inline(always)]
const fn new() -> Self {
Self::Inline(heapless::String::new())
}
}
impl<H> Repr<H>
where
H: Deref<Target = str> + for<'a> From<&'a str>,
{
#[inline(always)]
fn new_inline(text: &str) -> Option<Self> {
heapless::String::try_from(text).ok().map(Self::Inline)
}
#[inline(always)]
fn copy_from_str(text: &str) -> Self {
match heapless::String::try_from(text) {
Ok(buf) => Self::Inline(buf),
Err(_) => Self::Heap(text.into()),
}
}
#[inline(always)]
fn len(&self) -> usize {
match self {
Self::Heap(data) => data.len(),
Self::Inline(buf) => buf.len(),
}
}
#[inline(always)]
fn is_empty(&self) -> bool {
match self {
Self::Heap(data) => data.is_empty(),
Self::Inline(buf) => buf.is_empty(),
}
}
#[inline]
fn as_str(&self) -> &str {
match self {
Self::Heap(data) => data,
Self::Inline(buf) => buf.as_str(),
}
}
#[inline]
fn ptr_eq(&self, other: &Self) -> bool {
let (this, that) = (self.as_str(), other.as_str());
ptr::eq(this.as_ptr(), that.as_ptr()) && this.len() == that.len()
}
}
#[macro_export]
macro_rules! fmts {
($($tt:tt)*) => {{
let mut w = $crate::str::StrMut::default();
::std::fmt::Write::write_fmt(&mut w, format_args!($($tt)*))
.expect("a formatting trait implementation returned an error");
w.freeze()
}};
}
#[macro_export]
macro_rules! fmts_mut {
($($tt:tt)*) => {{
let mut w = $crate::str::StrMut::default();
::std::fmt::Write::write_fmt(&mut w, format_args!($($tt)*))
.expect("a formatting trait implementation returned an error");
w
}};
}
macro_rules! impl_extend {
(for<$lt:lifetime> $type:ty, ($this:ident, $item:ident) => $($body:tt)*) => {
impl<$lt> Extend<$type> for StrMut {
fn extend<T: IntoIterator<Item = $type>>(&mut self, iter: T) {
let $this: &mut StrMut = self;
for $item in iter {
$($body)*
}
}
}
impl<$lt> FromIterator<$type> for StrMut {
fn from_iter<T: IntoIterator<Item = $type>>(iter: T) -> Self {
let mut $this = StrMut::default();
$this.extend(iter);
$this
}
}
impl<$lt> FromIterator<$type> for Str {
fn from_iter<T: IntoIterator<Item = $type>>(iter: T) -> Self {
let mut $this = StrMut::default();
$this.extend(iter);
$this.freeze()
}
}
};
($type:ty, ($this:ident, $item:ident) => $($body:tt)*) => {
impl Extend<$type> for StrMut {
fn extend<T: IntoIterator<Item = $type>>(&mut self, iter: T) {
let $this: &mut StrMut = self;
for $item in iter {
$($body)*
}
}
}
impl FromIterator<$type> for StrMut {
fn from_iter<T: IntoIterator<Item = $type>>(iter: T) -> Self {
let mut $this = StrMut::default();
$this.extend(iter);
$this
}
}
impl FromIterator<$type> for Str {
fn from_iter<T: IntoIterator<Item = $type>>(iter: T) -> Self {
let mut $this = StrMut::default();
$this.extend(iter);
$this.freeze()
}
}
};
}
impl_extend!(char, (s, rhs) => s.push(rhs));
impl_extend!(String, (s, rhs) => s.push_str(rhs.as_str()));
impl_extend!(for<'a> &'a String, (s, rhs) => s.push_str(rhs.as_str()));
impl_extend!(for<'a> &'a str, (s, rhs) => s.push_str(rhs));
#[derive(Default, Clone)]
#[repr(transparent)]
pub struct StrMut(Repr<BytesStrMut>);
impl StrMut {
#[inline]
pub unsafe fn from_utf8_unchecked_owned(u: impl Into<BytesMut>) -> Self {
Self(Repr::Heap(unsafe { BytesStrMut::from_inner_unchecked(u.into()) }))
}
#[inline]
pub unsafe fn from_utf8_unchecked(u: &[u8]) -> Self {
Self::new(unsafe { str::from_utf8_unchecked(u) })
}
#[inline]
pub fn from_utf8_owned(u: impl Into<BytesMut>) -> Result<Self, Utf8ErrorMut> {
let u: BytesMut = u.into();
Ok(Self(Repr::Heap(BytesStrMut::from_inner(u)?)))
}
#[inline]
pub fn from_utf8(u: &[u8]) -> Result<Self, str::Utf8Error> {
Ok(Self::new(str::from_utf8(u)?))
}
#[inline]
pub fn new_inline(text: &str) -> Self {
Self(Repr::new_inline(text).expect("len <= INLINE_CAP"))
}
#[inline(always)]
pub fn new(text: impl AsRef<str>) -> Self {
Self(Repr::copy_from_str(text.as_ref()))
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
if capacity > INLINE_CAP {
Self(Repr::Heap(unsafe {
BytesStrMut::from_inner_unchecked(BytesMut::with_capacity(capacity))
}))
} else {
Self(Repr::new())
}
}
#[inline(always)]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline(always)]
pub fn as_str_mut(&mut self) -> &mut str {
match &mut self.0 {
Repr::Heap(data) => unsafe { str::from_utf8_unchecked_mut(data.as_bytes_mut()) },
Repr::Inline(buf) => buf.as_mut_str(),
}
}
#[inline(always)]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn truncate(&mut self, len: usize) {
match &mut self.0 {
Repr::Inline(buf) => {
buf.truncate(len);
},
Repr::Heap(heap) => {
assert!(heap.is_char_boundary(len), "Index is not on a char boundary");
unsafe {
heap.inner_mut().truncate(len);
}
},
}
}
#[inline(always)]
pub const fn is_spilled(&self) -> bool {
matches!(self.0, Repr::Heap(..))
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
match &mut self.0 {
Repr::Inline(buf) => {
let cap = buf.len() + additional;
if cap > INLINE_CAP {
let cap = cap.next_power_of_two();
let mut heap =
unsafe { BytesStrMut::from_inner_unchecked(BytesMut::with_capacity(cap)) };
heap.push_str(buf.as_str());
*self = Self(Repr::Heap(heap));
}
},
Repr::Heap(heap) => {
unsafe {
heap.inner_mut().reserve(additional);
}
},
}
}
#[must_use]
#[inline]
pub fn freeze(self) -> Str {
Str(match self.0 {
Repr::Inline(buf) => Repr::Inline(buf),
Repr::Heap(heap) => Repr::Heap(heap.freeze()),
})
}
#[inline]
pub fn push(&mut self, c: char) {
let mut buf = [0; 4];
self.push_str(c.encode_utf8(&mut buf));
}
#[inline]
pub fn push_str(&mut self, s: &str) {
match &mut self.0 {
Repr::Inline(buf) => {
let len = buf.len();
if buf.push_str(s).is_err() {
let mut heap = BytesMut::with_capacity((len + s.len()).next_power_of_two());
heap.extend_from_slice(buf.as_bytes());
heap.extend_from_slice(s.as_bytes());
*self = Self(Repr::Heap(unsafe { BytesStrMut::from_inner_unchecked(heap) }));
}
},
Repr::Heap(heap) => heap.push_str(s),
}
}
#[inline]
pub unsafe fn extend_from_bytes_unchecked(&mut self, s: &[u8]) {
match &mut self.0 {
Repr::Inline(buf) => {
let len = buf.len();
if unsafe { buf.as_mut_vec().extend_from_slice(s).is_err() } {
let mut heap = BytesMut::with_capacity((len + s.len()).next_power_of_two());
heap.extend_from_slice(buf.as_bytes());
heap.extend_from_slice(s);
*self = Self(Repr::Heap(unsafe { BytesStrMut::from_inner_unchecked(heap) }));
}
},
Repr::Heap(heap) => unsafe { heap.inner_mut().push_slice(s) },
}
}
#[inline]
pub fn insert(&mut self, index: usize, s: &str) {
match &mut self.0 {
Repr::Inline(buf) => {
assert!(
buf.is_char_boundary(index),
"index is not on a valid UTF-8 character boundary"
);
if buf.insert_str(index, s).is_err() {
let old_len = buf.len();
let new_len = old_len + s.len();
let mut heap = BytesMut::with_capacity(new_len.next_power_of_two());
heap.extend_from_slice(&buf.as_bytes()[..index]);
heap.extend_from_slice(s.as_bytes());
heap.extend_from_slice(&buf.as_bytes()[index..]);
*self = Self(Repr::Heap(unsafe { BytesStrMut::from_inner_unchecked(heap) }));
}
},
Repr::Heap(heap) => {
assert!(
heap.is_char_boundary(index),
"index is not on a valid UTF-8 character boundary"
);
let inner = unsafe { heap.inner_mut() };
let len = inner.len();
let string_len = s.len();
inner.reserve(string_len);
unsafe {
let ptr = inner.as_mut_ptr();
core::ptr::copy(ptr.add(index), ptr.add(index + string_len), len - index);
}
unsafe {
core::ptr::copy_nonoverlapping(
s.as_ptr(),
inner.as_mut_ptr().add(index),
string_len,
);
}
unsafe {
inner.set_len(len + string_len);
}
},
}
}
}
impl fmt::Write for StrMut {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
}
pub trait IntoStr: fmt::Display {
fn into_str(self) -> Str
where
Self: Sized,
{
self.to_str()
}
fn into_str_mut(self) -> StrMut
where
Self: Sized,
{
self.into_str().into()
}
fn to_str(&self) -> Str {
fmts!("{self}")
}
fn to_strmut(&self) -> StrMut {
self.to_str().into()
}
}
impl IntoStr for &str {
#[inline]
fn into_str(self) -> Str {
Str::new(self)
}
#[inline]
fn into_str_mut(self) -> StrMut {
StrMut::new(self)
}
#[inline]
fn to_str(&self) -> Str {
Str::new(self)
}
#[inline]
fn to_strmut(&self) -> StrMut {
StrMut::new(self)
}
}
impl IntoStr for &mut str {
#[inline]
fn into_str(self) -> Str {
Str::new(self)
}
#[inline]
fn into_str_mut(self) -> StrMut {
StrMut::new(self)
}
#[inline]
fn to_str(&self) -> Str {
Str::new(self)
}
#[inline]
fn to_strmut(&self) -> StrMut {
StrMut::new(self)
}
}
impl IntoStr for Str {
#[inline]
fn into_str(self) -> Str {
self
}
#[inline]
fn into_str_mut(self) -> StrMut {
self.into()
}
#[inline]
fn to_str(&self) -> Str {
self.clone()
}
#[inline]
fn to_strmut(&self) -> StrMut {
StrMut::new(self.as_str())
}
}
impl IntoStr for StrMut {
#[inline]
fn into_str(self) -> Str {
self.freeze()
}
#[inline]
fn into_str_mut(self) -> StrMut {
self
}
#[inline]
fn to_str(&self) -> Str {
Str::new(self.as_str())
}
#[inline]
fn to_strmut(&self) -> StrMut {
self.clone()
}
}
impl IntoStr for CowStr<'_> {
#[inline]
fn into_str(self) -> Str {
match self {
CowStr::Borrowed(s) => Str::new(s),
CowStr::Owned(s) => s.freeze(),
}
}
#[inline]
fn into_str_mut(self) -> StrMut {
match self {
CowStr::Borrowed(s) => StrMut::new(s),
CowStr::Owned(s) => s,
}
}
#[inline]
fn to_str(&self) -> Str {
Str::new(self.as_str())
}
#[inline]
fn to_strmut(&self) -> StrMut {
StrMut::new(self.as_str())
}
}
impl IntoStr for String {
#[inline]
fn into_str(self) -> Str {
Str(Repr::Heap(self.into()))
}
#[inline]
fn into_str_mut(self) -> StrMut {
StrMut(Repr::Heap(unsafe {
BytesStrMut::from_inner_unchecked(Bytes::from(self.into_bytes()).into())
}))
}
#[inline]
fn to_str(&self) -> Str {
self.as_str().into()
}
#[inline]
fn to_strmut(&self) -> StrMut {
self.as_str().into()
}
}
impl IntoStr for BytesStr {
#[inline]
fn into_str(self) -> Str {
Str(Repr::Heap(self))
}
#[inline]
fn into_str_mut(self) -> StrMut {
StrMut(Repr::Heap(unsafe { BytesStrMut::from_inner_unchecked(self.into_inner().into()) }))
}
#[inline]
fn to_str(&self) -> Str {
Str(Repr::Heap(self.clone()))
}
#[inline]
fn to_strmut(&self) -> StrMut {
self.deref().into()
}
}
impl IntoStr for Cow<'_, str> {
#[inline]
fn into_str(self) -> Str {
match self {
Cow::Borrowed(s) => Str::new(s),
Cow::Owned(s) => Str(Repr::Heap(s.into())),
}
}
#[inline]
fn into_str_mut(self) -> StrMut {
match self {
Cow::Borrowed(s) => StrMut::new(s),
Cow::Owned(s) => s.into_str_mut(),
}
}
#[inline]
fn to_str(&self) -> Str {
match self {
Cow::Borrowed(s) => Str::new(s),
Cow::Owned(s) => s.into_str(),
}
}
#[inline]
fn to_strmut(&self) -> StrMut {
match self {
Cow::Borrowed(s) => StrMut::new(s),
Cow::Owned(s) => s.into_str_mut(),
}
}
}
impl IntoStr for Box<str> {
#[inline]
fn into_str(self) -> Str {
Str(Repr::Heap(self.into()))
}
#[inline]
fn into_str_mut(self) -> StrMut {
StrMut(Repr::Heap(unsafe {
BytesStrMut::from_inner_unchecked(Bytes::from(self.into_boxed_bytes()).into())
}))
}
#[inline]
fn to_str(&self) -> Str {
Str::new(self.as_ref())
}
#[inline]
fn to_strmut(&self) -> StrMut {
StrMut::new(self.as_ref())
}
}
impl IntoStr for Arc<str> {
#[inline]
fn into_str(self) -> Str {
let bytes: Arc<[u8]> = self.into();
Str(Repr::Heap(unsafe { BytesStr::from_inner_unchecked(Bytes::from_owner(bytes)) }))
}
#[inline]
fn into_str_mut(self) -> StrMut {
StrMut::new(self.as_ref())
}
#[inline]
fn to_str(&self) -> Str {
Str::new(self.as_ref())
}
#[inline]
fn to_strmut(&self) -> StrMut {
StrMut::new(self.as_ref())
}
}
impl<T> IntoStr for &T
where
T: fmt::Display + ?Sized,
{
default fn into_str(self) -> Str {
fmts!("{}", self)
}
#[inline]
default fn into_str_mut(self) -> StrMut {
fmts_mut!("{}", self)
}
#[inline]
default fn to_str(&self) -> Str {
fmts!("{}", *self)
}
#[inline]
default fn to_strmut(&self) -> StrMut {
fmts_mut!("{}", *self)
}
}
impl str::FromStr for Str {
type Err = Infallible;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s))
}
}
impl From<fmt::Arguments<'_>> for Str {
#[inline]
fn from(args: fmt::Arguments<'_>) -> Self {
args.into_str()
}
}
impl From<&str> for Str {
#[inline]
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<&mut str> for Str {
#[inline]
fn from(s: &mut str) -> Self {
Self::new(s)
}
}
impl From<&str> for StrMut {
#[inline]
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<&mut str> for StrMut {
#[inline]
fn from(s: &mut str) -> Self {
Self::new(s)
}
}
impl From<&String> for Str {
#[inline]
fn from(s: &String) -> Self {
Self::new(s)
}
}
impl From<&String> for StrMut {
#[inline]
fn from(s: &String) -> Self {
Self::new(s)
}
}
impl From<String> for Str {
#[inline(always)]
fn from(text: String) -> Self {
Self(Repr::Heap(text.into()))
}
}
impl From<String> for StrMut {
#[inline(always)]
fn from(text: String) -> Self {
Self(Repr::Heap(unsafe {
BytesStrMut::from_inner_unchecked(Bytes::from(text.into_bytes()).into())
}))
}
}
impl From<&BytesStr> for Str {
#[inline]
fn from(s: &BytesStr) -> Self {
Self(Repr::Heap(s.clone()))
}
}
impl From<&BytesStr> for StrMut {
#[inline]
fn from(s: &BytesStr) -> Self {
Self::new(&**s)
}
}
impl From<BytesStr> for Str {
#[inline(always)]
fn from(text: BytesStr) -> Self {
Self(Repr::Heap(text))
}
}
impl From<BytesStr> for StrMut {
#[inline(always)]
fn from(text: BytesStr) -> Self {
Self(Repr::Heap(unsafe {
BytesStrMut::from_inner_unchecked(BytesMut::from(text.into_inner()))
}))
}
}
impl From<BytesStrMut> for Str {
#[inline]
fn from(value: BytesStrMut) -> Self {
Self(Repr::Heap(value.freeze()))
}
}
impl From<BytesStrMut> for StrMut {
#[inline]
fn from(value: BytesStrMut) -> Self {
Self(Repr::Heap(value))
}
}
impl<'a> From<Cow<'a, str>> for Str {
#[inline]
fn from(s: Cow<'a, str>) -> Self {
match s {
Cow::Borrowed(borrowed) => Self::new(borrowed),
Cow::Owned(owned) => Self(Repr::Heap(owned.into())),
}
}
}
impl<'a> From<Cow<'a, str>> for StrMut {
#[inline]
fn from(s: Cow<'a, str>) -> Self {
match s {
Cow::Borrowed(borrowed) => borrowed.into(),
Cow::Owned(owned) => owned.into(),
}
}
}
impl From<Str> for BytesStr {
#[inline(always)]
fn from(text: Str) -> Self {
match text.0 {
Repr::Heap(data) => data,
_ => text.as_str().into(),
}
}
}
impl From<StrMut> for BytesStr {
#[inline(always)]
fn from(text: StrMut) -> Self {
match text.0 {
Repr::Heap(data) => data.freeze(),
_ => text.as_str().into(),
}
}
}
impl From<Str> for String {
#[inline(always)]
fn from(text: Str) -> Self {
text.as_str().into()
}
}
impl From<StrMut> for String {
#[inline(always)]
fn from(text: StrMut) -> Self {
text.as_str().into()
}
}
impl From<Str> for Bytes {
#[inline(always)]
fn from(text: Str) -> Self {
match text.0 {
Repr::Heap(data) => data.into(),
Repr::Inline(buf) => Self::copy_from_slice(buf.as_bytes()),
}
}
}
impl From<StrMut> for Bytes {
#[inline(always)]
fn from(text: StrMut) -> Self {
match text.0 {
Repr::Heap(data) => data.into_inner().into(),
Repr::Inline(buf) => Self::copy_from_slice(buf.as_bytes()),
}
}
}
impl From<Str> for BytesMut {
#[inline(always)]
fn from(value: Str) -> Self {
match value.0 {
Repr::Heap(data) => data.into_inner().into(),
Repr::Inline(buf) => Self::from(buf.as_bytes()),
}
}
}
impl From<StrMut> for BytesMut {
#[inline(always)]
fn from(text: StrMut) -> Self {
match text.0 {
Repr::Heap(data) => data.into_inner(),
Repr::Inline(buf) => Self::from(buf.as_bytes()),
}
}
}
impl From<Str> for BytesStrMut {
#[inline(always)]
fn from(value: Str) -> Self {
unsafe { Self::from_inner_unchecked(BytesMut::from(value)) }
}
}
impl From<StrMut> for BytesStrMut {
#[inline(always)]
fn from(value: StrMut) -> Self {
match value.0 {
Repr::Heap(data) => data,
Repr::Inline(buf) => unsafe { Self::from_inner_unchecked(BytesMut::from(buf.as_bytes())) },
}
}
}
impl From<StrMut> for Str {
#[inline]
fn from(value: StrMut) -> Self {
value.freeze()
}
}
impl From<Str> for StrMut {
#[inline]
fn from(value: Str) -> Self {
match value.0 {
Repr::Inline(buf) => Self(Repr::Inline(buf)),
Repr::Heap(heap) => unsafe { Self::from_utf8_unchecked_owned(heap.into_inner()) },
}
}
}
impl From<Arc<str>> for Str {
#[inline]
fn from(value: Arc<str>) -> Self {
let bytes: Arc<[u8]> = value.into();
Self(Repr::Heap(unsafe { BytesStr::from_inner_unchecked(Bytes::from_owner(bytes)) }))
}
}
impl From<Box<str>> for Str {
#[inline]
fn from(value: Box<str>) -> Self {
Self(Repr::Heap(value.into()))
}
}
impl From<Box<str>> for StrMut {
#[inline]
fn from(value: Box<str>) -> Self {
Self(Repr::Heap(unsafe {
BytesStrMut::from_inner_unchecked(Bytes::from(value.into_boxed_bytes()).into())
}))
}
}
impl serde::Serialize for Str {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Str {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Error, Unexpected};
struct StrVisitor;
impl serde::de::Visitor<'_> for StrVisitor {
type Value = Str;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
Ok(Str::from(v))
}
fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
Ok(Str::from(v))
}
fn visit_bytes<E: Error>(self, v: &[u8]) -> Result<Self::Value, E> {
match str::from_utf8(v) {
Ok(s) => Ok(Str::from(s)),
Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
}
}
fn visit_byte_buf<E: Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
match String::from_utf8(v) {
Ok(s) => Ok(Str::from(s)),
Err(e) => Err(Error::invalid_value(Unexpected::Bytes(&e.into_bytes()), &self)),
}
}
}
deserializer.deserialize_str(StrVisitor)
}
}
impl serde::Serialize for CowStr<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
impl<'de: 'a, 'a> serde::Deserialize<'de> for CowStr<'a> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Error, Unexpected};
struct CowStrVisitor;
impl<'de> serde::de::Visitor<'de> for CowStrVisitor {
type Value = CowStr<'de>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
Ok(CowStr::Owned(StrMut::from(v)))
}
fn visit_borrowed_str<E: Error>(self, v: &'de str) -> Result<Self::Value, E> {
Ok(CowStr::Borrowed(v))
}
fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
Ok(CowStr::Owned(StrMut::from(v)))
}
fn visit_bytes<E: Error>(self, v: &[u8]) -> Result<Self::Value, E> {
match str::from_utf8(v) {
Ok(s) => Ok(CowStr::Owned(StrMut::from(s))),
Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
}
}
fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match str::from_utf8(v) {
Ok(s) => Ok(CowStr::Borrowed(s)),
Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
}
}
fn visit_byte_buf<E: Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
match String::from_utf8(v) {
Ok(s) => Ok(CowStr::Owned(StrMut::from(s))),
Err(e) => Err(Error::invalid_value(Unexpected::Bytes(&e.into_bytes()), &self)),
}
}
}
deserializer.deserialize_str(CowStrVisitor)
}
}
#[derive(Clone)]
pub enum CowStr<'a> {
Borrowed(&'a str),
Owned(StrMut),
}
impl<'a> CowStr<'a> {
#[inline]
pub const fn from_str(s: &'a str) -> Self {
CowStr::Borrowed(s)
}
#[inline]
pub const fn from_owned(s: StrMut) -> Self {
CowStr::Owned(s)
}
#[inline]
pub fn as_str(&self) -> &str {
match self {
CowStr::Borrowed(s) => s,
CowStr::Owned(s) => s.as_str(),
}
}
#[inline]
pub fn len(&self) -> usize {
self.as_str().len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
#[inline]
pub const fn is_borrowed(&self) -> bool {
matches!(self, CowStr::Borrowed(_))
}
#[inline]
pub const fn is_owned(&self) -> bool {
matches!(self, CowStr::Owned(_))
}
#[inline]
pub fn assign_slice_ref(&mut self, subset: &'a str) {
match self {
CowStr::Owned(owned) => {
unsafe {
std::ptr::copy(subset.as_ptr(), owned.as_mut_ptr(), subset.len());
owned.truncate(subset.len());
}
},
CowStr::Borrowed(_) => *self = CowStr::Borrowed(subset),
}
}
#[inline]
pub fn assign_range(&mut self, range: impl Into<std::ops::Range<usize>>) {
let range = range.into();
match self {
CowStr::Owned(owned) => {
let len = range.end - range.start;
unsafe {
if range.start > 0 {
let base = owned.as_str_mut().as_mut_ptr();
std::ptr::copy(base.add(range.start).cast_const(), base, len);
}
owned.truncate(len);
}
},
CowStr::Borrowed(s) => {
*self = CowStr::Borrowed(&s[range]);
},
}
}
#[inline]
pub fn into_range(self, range: impl Into<std::ops::Range<usize>>) -> Self {
let range = range.into();
match self {
CowStr::Borrowed(s) => CowStr::Borrowed(&s[range]),
CowStr::Owned(mut owned) => {
let len = range.end - range.start;
unsafe {
if range.start > 0 {
let base = owned.as_str_mut().as_mut_ptr();
std::ptr::copy(base.add(range.start).cast_const(), base, len);
}
owned.truncate(len);
}
CowStr::Owned(owned)
},
}
}
#[inline]
pub fn into_slice_ref(self, subset: &'a str) -> Self {
match self {
CowStr::Borrowed(_) => CowStr::Borrowed(subset),
CowStr::Owned(mut owned) => {
unsafe {
std::ptr::copy(subset.as_ptr(), owned.as_mut_ptr(), subset.len());
owned.truncate(subset.len());
}
CowStr::Owned(owned)
},
}
}
#[inline]
pub fn into_owned(self) -> CowStr<'static> {
match self {
CowStr::Borrowed(s) => CowStr::Owned(s.into()),
CowStr::Owned(owned) => CowStr::Owned(owned),
}
}
#[inline]
pub fn borrow(&self) -> CowStr<'_> {
match self {
CowStr::Borrowed(s) => CowStr::Borrowed(s),
CowStr::Owned(o) => CowStr::Borrowed(o.as_str()),
}
}
#[inline]
pub fn push_str(&mut self, s: &str) {
self.as_mut().push_str(s);
}
#[inline]
pub fn push(&mut self, c: char) {
self.as_mut().push(c);
}
#[inline]
pub fn truncate(&mut self, len: usize) {
if self.len() > len {
match self {
CowStr::Borrowed(s) => {
*self = CowStr::Borrowed(s.split_at(len).0);
},
CowStr::Owned(s) => {
s.truncate(len);
},
}
}
}
#[inline]
pub fn trim(&mut self) {
match self {
CowStr::Borrowed(s) => {
*self = CowStr::Borrowed(s.trim());
},
CowStr::Owned(s) => {
let range = s
.substr_range(s.trim())
.expect("substr_range should not fail");
self.assign_range(range);
},
}
}
#[inline]
pub fn trim_end(&mut self) {
match self {
CowStr::Borrowed(s) => {
*self = CowStr::Borrowed(s.trim_end());
},
CowStr::Owned(s) => {
let range = s
.substr_range(s.trim_end())
.expect("substr_range should not fail");
self.assign_range(range);
},
}
}
#[inline]
pub fn trim_start(&mut self) {
match self {
CowStr::Borrowed(s) => {
*self = CowStr::Borrowed(s.trim_start());
},
CowStr::Owned(s) => {
let range = s
.substr_range(s.trim_start())
.expect("substr_range should not fail");
self.assign_range(range);
},
}
}
#[inline]
pub fn make_ascii_lowercase(&mut self) {
if self.as_str().bytes().any(|b| b.is_ascii_uppercase()) {
self.as_mut().make_ascii_lowercase();
}
}
#[inline]
pub fn make_ascii_uppercase(&mut self) {
if self.as_str().bytes().any(|b| b.is_ascii_lowercase()) {
self.as_mut().make_ascii_uppercase();
}
}
}
impl AsMut<StrMut> for CowStr<'_> {
fn as_mut(&mut self) -> &mut StrMut {
match self {
CowStr::Borrowed(s) => {
*self = CowStr::Owned(StrMut::new(s));
match self {
CowStr::Owned(owned) => owned,
_ => unreachable!(),
}
},
CowStr::Owned(owned) => owned,
}
}
}
impl<'a> From<&'a str> for CowStr<'a> {
#[inline]
fn from(s: &'a str) -> Self {
CowStr::Borrowed(s)
}
}
impl From<StrMut> for CowStr<'_> {
#[inline]
fn from(s: StrMut) -> Self {
CowStr::Owned(s)
}
}
impl From<Str> for CowStr<'_> {
#[inline]
fn from(s: Str) -> Self {
CowStr::Owned(StrMut::from(s))
}
}
impl From<String> for CowStr<'_> {
#[inline]
fn from(s: String) -> Self {
CowStr::Owned(StrMut::from(s))
}
}
impl<'a> From<Cow<'a, str>> for CowStr<'a> {
#[inline]
fn from(cow: Cow<'a, str>) -> Self {
match cow {
Cow::Borrowed(s) => CowStr::Borrowed(s),
Cow::Owned(s) => CowStr::Owned(StrMut::from(s)),
}
}
}
impl<'a> From<CowStr<'a>> for Cow<'a, str> {
#[inline]
fn from(cow: CowStr<'a>) -> Self {
match cow {
CowStr::Borrowed(s) => Cow::Borrowed(s),
CowStr::Owned(s) => Cow::Owned(s.as_str().to_string()),
}
}
}
impl<'a> From<CowStr<'a>> for Str {
#[inline]
fn from(cow: CowStr<'a>) -> Self {
match cow {
CowStr::Borrowed(s) => Self::from(s),
CowStr::Owned(s) => Self::from(s),
}
}
}
impl<'a> From<CowStr<'a>> for StrMut {
#[inline]
fn from(cow: CowStr<'a>) -> Self {
match cow {
CowStr::Borrowed(s) => Self::from(s),
CowStr::Owned(s) => s,
}
}
}
impl Deref for CowStr<'_> {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for CowStr<'_> {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for CowStr<'_> {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_str().as_bytes()
}
}
impl Borrow<str> for CowStr<'_> {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for CowStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl fmt::Debug for CowStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl PartialEq for CowStr<'_> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for CowStr<'_> {}
impl PartialEq<str> for CowStr<'_> {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<CowStr<'_>> for str {
#[inline]
fn eq(&self, other: &CowStr<'_>) -> bool {
self == other.as_str()
}
}
impl PartialEq<&str> for CowStr<'_> {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialEq<CowStr<'_>> for &str {
#[inline]
fn eq(&self, other: &CowStr<'_>) -> bool {
*self == other.as_str()
}
}
impl PartialEq<String> for CowStr<'_> {
#[inline]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<CowStr<'_>> for String {
#[inline]
fn eq(&self, other: &CowStr<'_>) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<Str> for CowStr<'_> {
#[inline]
fn eq(&self, other: &Str) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<CowStr<'_>> for Str {
#[inline]
fn eq(&self, other: &CowStr<'_>) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<StrMut> for CowStr<'_> {
#[inline]
fn eq(&self, other: &StrMut) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialEq<CowStr<'_>> for StrMut {
#[inline]
fn eq(&self, other: &CowStr<'_>) -> bool {
self.as_str() == other.as_str()
}
}
impl PartialOrd for CowStr<'_> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CowStr<'_> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl PartialOrd<str> for CowStr<'_> {
#[inline(always)]
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
self.as_str().partial_cmp(other)
}
}
impl PartialOrd<CowStr<'_>> for str {
#[inline(always)]
fn partial_cmp(&self, other: &CowStr<'_>) -> Option<Ordering> {
self.partial_cmp(other.as_str())
}
}
impl PartialOrd<&str> for CowStr<'_> {
#[inline(always)]
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
self.partial_cmp(*other)
}
}
impl PartialOrd<CowStr<'_>> for &str {
#[inline(always)]
fn partial_cmp(&self, other: &CowStr<'_>) -> Option<Ordering> {
(*self).partial_cmp(other)
}
}
impl Hash for CowStr<'_> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl Default for CowStr<'_> {
#[inline]
fn default() -> Self {
CowStr::Owned(StrMut::default())
}
}
#[cfg(test)]
mod tests {
use serde_json as json;
use super::*;
const PREFIX: &str = "prefix__";
const REMAINDER: &str = "abcdefghijklmnopjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const LONG_TEXT: &str = "prefix__abcdefghijklmnopjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const SPACED: &str = " prefix__abcdefghijklmnopjklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
fn heap_ptr(s: &Str) -> (*const u8, usize) {
match &s.0 {
Repr::Heap(data) => (data.inner().as_ptr(), data.len()),
_ => panic!("expected heap representation"),
}
}
#[test]
fn shared_buffer_prefix_slice_is_not_equal_to_parent() {
let parent = Str::new(LONG_TEXT);
let prefix = parent.slice(..PREFIX.len());
assert_eq!(heap_ptr(&prefix).0, heap_ptr(&parent).0);
assert_ne!(prefix, parent);
assert_ne!(parent, prefix);
assert_eq!(prefix, Str::new(PREFIX));
assert_eq!(parent, parent.slice(..));
}
#[test]
fn strip_prefix_reuses_heap_storage() {
let value = Str::new(LONG_TEXT);
assert!(value.is_spilled());
let (orig_ptr, orig_len) = heap_ptr(&value);
assert_eq!(orig_len, LONG_TEXT.len());
let remainder = value.strip_prefix(PREFIX).expect("prefix matches");
assert_eq!(remainder.as_str(), REMAINDER);
let (rem_ptr, rem_len) = heap_ptr(&remainder);
assert_eq!(rem_len, REMAINDER.len());
unsafe {
assert_eq!(rem_ptr.offset_from(orig_ptr) as usize, PREFIX.len());
}
}
#[test]
fn split_at_reuses_heap_storage() {
let value = Str::new(LONG_TEXT);
assert!(value.is_spilled());
let split_at = PREFIX.len();
let (left, right) = value.split_at(split_at);
assert_eq!(left.as_str(), &LONG_TEXT[..split_at]);
assert_eq!(right.as_str(), &LONG_TEXT[split_at..]);
let (orig_ptr, _) = heap_ptr(&value);
let (left_ptr, left_len) = heap_ptr(&left);
let (right_ptr, right_len) = heap_ptr(&right);
assert_eq!(left_len, split_at);
assert_eq!(right_len, LONG_TEXT.len() - split_at);
unsafe {
assert_eq!(left_ptr.offset_from(orig_ptr) as usize, 0);
assert_eq!(right_ptr.offset_from(orig_ptr) as usize, split_at);
}
}
#[test]
fn trim_start_reuses_heap_storage() {
let value = Str::new(SPACED);
assert!(value.is_spilled());
let trimmed = value.trim_start();
assert_eq!(trimmed.as_str(), LONG_TEXT);
let (orig_ptr, _) = heap_ptr(&value);
let (trim_ptr, _) = heap_ptr(&trimmed);
unsafe {
assert_eq!(trim_ptr.offset_from(orig_ptr) as usize, 3);
}
}
#[test]
fn test_add_operations() {
let s1 = Str::new("hello");
let result = s1 + " world";
assert_eq!(result.as_str(), "hello world");
let s2 = Str::new("world");
let result = "hello " + s2;
assert_eq!(result.as_str(), "hello world");
let s3 = Str::new("hello");
let result = &s3 + " world";
assert_eq!(result.as_str(), "hello world");
let s4 = Str::new("world");
let result = "hello " + &s4;
assert_eq!(result.as_str(), "hello world");
let m1 = StrMut::new("hello");
let result = m1 + " world";
assert_eq!(result.as_str(), "hello world");
let m2 = StrMut::new("world");
let result = "hello " + m2;
assert_eq!(result.as_str(), "hello world");
let m3 = StrMut::new("hello");
let result = &m3 + " world";
assert_eq!(result.as_str(), "hello world");
let m4 = StrMut::new("world");
let result = "hello " + &m4;
assert_eq!(result.as_str(), "hello world");
let long = Str::new("this is a very long string that will be heap allocated");
let result = &long + " and more";
assert_eq!(
result.as_str(),
"this is a very long string that will be heap allocated and more"
);
}
#[test]
fn test_insert_inline_to_heap_promotion() {
let mut s = StrMut::new("hello world");
assert!(!s.is_spilled());
s.insert(6, "beautiful and wonderful ");
assert_eq!(s.as_str(), "hello beautiful and wonderful world");
assert!(s.is_spilled());
let mut s = StrMut::new("world");
s.insert(0, "hello ");
assert_eq!(s.as_str(), "hello world");
let mut s = StrMut::new("hello");
s.insert(5, " world");
assert_eq!(s.as_str(), "hello world");
let mut s = StrMut::new("12345678901234567890"); s.insert(10, "ABCDE"); assert_eq!(s.as_str(), "1234567890ABCDE1234567890");
assert!(s.is_spilled());
}
#[test]
fn test_cow_basic_operations() {
let s = "hello world";
let cow = CowStr::from(s);
assert!(cow.is_borrowed());
assert!(!cow.is_owned());
assert_eq!(cow.as_str(), "hello world");
assert_eq!(cow.len(), 11);
assert!(!cow.is_empty());
let mut_str = StrMut::new("hello");
let cow = CowStr::from(mut_str);
assert!(!cow.is_borrowed());
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello");
}
#[test]
fn test_cow_to_mut() {
let s = "hello";
let mut cow = CowStr::from(s);
assert!(cow.is_borrowed());
let mutable = cow.as_mut();
mutable.push_str(" world");
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello world");
let mutable2 = cow.as_mut();
mutable2.push_str("!");
assert_eq!(cow.as_str(), "hello world!");
}
#[test]
fn test_cow_assign_slice_ref() {
let original = "hello world";
let mut cow = CowStr::from(original);
cow.assign_slice_ref("goodbye");
assert!(cow.is_borrowed());
assert_eq!(cow.as_str(), "goodbye");
let mut cow = CowStr::from(StrMut::new("hello world"));
cow.assign_slice_ref("bye");
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "bye");
}
#[test]
fn test_cow_assign_range() {
let mut cow = CowStr::from("hello world");
cow.assign_range(0..5);
assert!(cow.is_borrowed());
assert_eq!(cow.as_str(), "hello");
let mut cow = CowStr::from(StrMut::new("hello world"));
cow.assign_range(6..11);
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "world");
let mut cow = CowStr::from(StrMut::new("hello world"));
cow.assign_range(3..8);
assert_eq!(cow.as_str(), "lo wo");
}
#[test]
fn test_cow_into_range() {
let cow = CowStr::from("hello world");
let new_cow = cow.into_range(0..5);
assert!(new_cow.is_borrowed());
assert_eq!(new_cow.as_str(), "hello");
let cow = CowStr::from(StrMut::new("hello world"));
let new_cow = cow.into_range(6..11);
assert!(new_cow.is_owned());
assert_eq!(new_cow.as_str(), "world");
}
#[test]
fn test_cow_into_slice_ref() {
let original = "hello world";
let cow = CowStr::from(original);
let subset = &original[6..11];
let new_cow = cow.into_slice_ref(subset);
assert!(new_cow.is_borrowed());
assert_eq!(new_cow.as_str(), "world");
let cow = CowStr::from(StrMut::new("hello world"));
let new_cow = cow.into_slice_ref("new");
assert!(new_cow.is_owned());
assert_eq!(new_cow.as_str(), "new");
}
#[test]
fn test_cow_mutating_operations() {
let mut cow = CowStr::from("hello");
cow.push_str(" world");
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello world");
let mut cow = CowStr::from("hello");
cow.push('!');
assert_eq!(cow.as_str(), "hello!");
let mut cow = CowStr::from("hello world");
cow.truncate(5);
assert!(cow.is_borrowed());
assert_eq!(cow.as_str(), "hello");
let mut cow = CowStr::from(StrMut::new("hello world"));
cow.truncate(5);
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello");
}
#[test]
fn test_cow_trim_operations() {
let mut cow = CowStr::from(" hello world ");
cow.trim();
assert!(cow.is_borrowed());
assert_eq!(cow.as_str(), "hello world");
let mut cow = CowStr::from(StrMut::new(" hello world "));
cow.trim();
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello world");
let mut cow = CowStr::from(" hello");
cow.trim_start();
assert!(cow.is_borrowed());
assert_eq!(cow.as_str(), "hello");
let mut cow = CowStr::from(StrMut::new(" hello"));
cow.trim_start();
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello");
let mut cow = CowStr::from("hello ");
cow.trim_end();
assert!(cow.is_borrowed());
assert_eq!(cow.as_str(), "hello");
let mut cow = CowStr::from(StrMut::new("hello "));
cow.trim_end();
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello");
}
#[test]
fn test_cow_ascii_case_conversion() {
let mut cow = CowStr::from("hello");
cow.make_ascii_lowercase();
assert!(cow.is_borrowed());
let mut cow = CowStr::from("HELLO");
cow.make_ascii_lowercase();
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello");
let mut cow = CowStr::from("HELLO");
cow.make_ascii_uppercase();
assert!(cow.is_borrowed());
let mut cow = CowStr::from("hello");
cow.make_ascii_uppercase();
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "HELLO");
}
#[test]
fn test_cow_equality() {
let cow1 = CowStr::from("hello");
let cow2 = CowStr::from(StrMut::new("hello"));
assert_eq!(cow1, cow2);
assert_eq!(cow1, "hello");
assert_eq!("hello", cow1);
assert_eq!(cow1, String::from("hello"));
assert_eq!(cow1, Str::new("hello"));
assert_eq!(cow1, StrMut::new("hello"));
}
#[test]
fn test_cow_ordering() {
let cow1 = CowStr::from("apple");
let cow2 = CowStr::from("banana");
let cow3 = CowStr::from(StrMut::new("apple"));
assert!(cow1 < cow2);
assert!(cow2 > cow1);
assert_eq!(cow1.cmp(&cow3), std::cmp::Ordering::Equal);
}
#[test]
fn test_cow_hash() {
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
let cow1 = CowStr::from("hello");
let cow2 = CowStr::from(StrMut::new("hello"));
let mut hasher1 = DefaultHasher::new();
cow1.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
cow2.hash(&mut hasher2);
let hash2 = hasher2.finish();
assert_eq!(hash1, hash2);
}
#[test]
fn test_cow_into_owned() {
let cow = CowStr::from("hello");
let owned = cow.into_str_mut();
assert_eq!(owned.as_str(), "hello");
let cow = CowStr::from(StrMut::new("world"));
let owned = cow.into_str_mut();
assert_eq!(owned.as_str(), "world");
}
#[test]
fn test_cow_into_str() {
let cow = CowStr::from("hello");
let string = cow.into_str();
assert_eq!(string.as_str(), "hello");
}
#[test]
fn test_cow_cow_conversion() {
let std_cow = Cow::Borrowed("hello");
let cow: CowStr = std_cow.into();
assert!(cow.is_borrowed());
let std_cow = Cow::<str>::Owned(String::from("world"));
let cow: CowStr = std_cow.into();
assert!(cow.is_owned());
let cow = CowStr::from("hello");
let std_cow: Cow<str> = cow.into();
assert!(matches!(std_cow, Cow::Borrowed(_)));
}
#[test]
fn test_cow_from_conversions() {
let string = Str::new("hello");
let cow: CowStr = string.into();
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "hello");
let string = String::from("world");
let cow: CowStr = string.into();
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "world");
let cow = CowStr::from("test");
let string: Str = cow.into();
assert_eq!(string.as_str(), "test");
let cow = CowStr::from("test");
let string_mut: StrMut = cow.into();
assert_eq!(string_mut.as_str(), "test");
}
#[test]
fn test_cow_default() {
let cow: CowStr = Default::default();
assert!(cow.is_owned());
assert_eq!(cow.as_str(), "");
assert!(cow.is_empty());
}
#[test]
fn test_cow_deref_and_borrow() {
let cow = CowStr::from("hello world");
assert_eq!(&*cow, "hello world");
let s: &str = cow.as_ref();
assert_eq!(s, "hello world");
let bytes: &[u8] = cow.as_ref();
assert_eq!(bytes, b"hello world");
}
#[test]
fn test_cow_serde() {
let cow = CowStr::from("hello world");
let json = json::to_string(&cow).unwrap();
assert_eq!(json, r#""hello world""#);
let deserialized: CowStr = json::from_str(&json).unwrap();
assert!(!deserialized.is_owned());
assert_eq!(deserialized.as_str(), "hello world");
let cow = CowStr::from(StrMut::new("test"));
let json = json::to_string(&cow).unwrap();
assert_eq!(json, r#""test""#);
}
#[test]
fn test_empty_strings() {
let s = Str::new("");
assert!(s.is_empty());
assert_eq!(s.len(), 0);
assert!(!s.is_spilled());
assert_eq!(s.as_str(), "");
let mut m = StrMut::new("");
assert!(m.is_empty());
assert_eq!(m.len(), 0);
assert!(!m.is_spilled());
m.push_str("");
assert!(m.is_empty());
assert_eq!(s.trim(), "");
assert_eq!(s.trim_start(), "");
assert_eq!(s.trim_end(), "");
assert_eq!(s.strip_prefix("x"), None);
assert_eq!(s.strip_suffix("x"), None);
let (l, r) = s.split_at(0);
assert!(l.is_empty());
assert!(r.is_empty());
let parts: Vec<_> = s.split("x").collect();
assert_eq!(parts.len(), 1);
assert_eq!(parts[0], "");
}
#[test]
fn test_exact_inline_capacity() {
let text = "12345678901234567890123"; assert_eq!(text.len(), 23);
let s = Str::new(text);
assert!(!s.is_spilled());
assert_eq!(s.len(), 23);
assert_eq!(s.as_str(), text);
let m = StrMut::new(text);
assert!(!m.is_spilled());
assert_eq!(m.len(), 23);
}
#[test]
fn test_boundary_24_bytes() {
let text = "123456789012345678901234"; assert_eq!(text.len(), 24);
let s = Str::new(text);
assert!(s.is_spilled());
assert_eq!(s.len(), 24);
assert_eq!(s.as_str(), text);
let m = StrMut::new(text);
assert!(m.is_spilled());
assert_eq!(m.len(), 24);
}
#[test]
fn test_slice_boundaries() {
let text = "0123456789";
let s = Str::new(text);
assert_eq!(s.slice(..), text);
assert_eq!(s.slice(0..10), text);
assert_eq!(s.slice(0..0), "");
assert_eq!(s.slice(5..5), "");
assert_eq!(s.slice(10..10), "");
assert_eq!(s.slice(0..1), "0");
assert_eq!(s.slice(9..10), "9");
assert_eq!(s.slice(..5), "01234");
assert_eq!(s.slice(5..), "56789");
}
#[test]
#[should_panic(expected = "self.is_char_boundary(new_len)")]
fn test_truncate_non_char_boundary_inline() {
let mut s = Str::new("hello world 世界");
s.truncate(13); }
#[test]
#[should_panic(expected = "Index is not on a char boundary")]
fn test_truncate_non_char_boundary_heap() {
let mut s = Str::new("hello world hello world 世界");
s.truncate(26); }
#[test]
fn test_from_utf8_valid() {
let valid = b"hello world";
let s = Str::from_utf8(valid).unwrap();
assert_eq!(s.as_str(), "hello world");
let valid_utf8 = "hello 世界 🌍".as_bytes();
let s = Str::from_utf8(valid_utf8).unwrap();
assert_eq!(s.as_str(), "hello 世界 🌍");
}
#[test]
fn test_from_utf8_invalid() {
let invalid = &[0xff, 0xfe, 0xfd];
assert!(Str::from_utf8(invalid).is_err());
let invalid = &[0xc0, 0x80]; assert!(Str::from_utf8(invalid).is_err());
}
#[test]
fn test_from_utf8_lossy() {
let valid = Str::from_utf8_lossy("hello 世界".as_bytes());
assert_eq!(valid.as_str(), "hello 世界");
let invalid = Str::from_utf8_lossy(b"hello \xff world");
assert_eq!(invalid.as_str(), "hello � world");
}
#[test]
fn test_from_utf8_owned_valid() {
let valid = bytes::Bytes::from("hello world");
let s = Str::from_utf8_owned(valid).unwrap();
assert_eq!(s.as_str(), "hello world");
assert!(s.is_spilled());
}
#[test]
fn test_from_utf8_owned_invalid() {
let invalid = bytes::Bytes::from_static(&[0xff, 0xfe, 0xfd]);
assert!(Str::from_utf8_owned(invalid).is_err());
}
#[test]
fn test_from_utf8_mut_valid() {
let valid = b"hello";
let m = StrMut::from_utf8(valid).unwrap();
assert_eq!(m.as_str(), "hello");
}
#[test]
fn test_from_utf8_mut_invalid() {
let invalid = &[0xff, 0xfe];
assert!(StrMut::from_utf8(invalid).is_err());
}
#[test]
fn test_multibyte_char_boundaries() {
let text = "🌍世界";
let s = Str::new(text);
assert_eq!(s.len(), 10);
let (l, r) = s.split_at(4);
assert_eq!(l.as_str(), "🌍");
assert_eq!(r.as_str(), "世界");
let mut s2 = Str::new(text);
s2.truncate(4);
assert_eq!(s2.as_str(), "🌍");
}
#[test]
#[should_panic(expected = "byte index 1 is not a char boundary")]
fn test_split_at_invalid_boundary() {
let s = Str::new("世界");
let _ = s.split_at(1); }
#[test]
#[should_panic(expected = "len <= INLINE_CAP")]
fn test_new_inline_panic() {
let too_long = "123456789012345678901234"; let _ = Str::new_inline(too_long);
}
#[test]
#[should_panic(expected = "len <= INLINE_CAP")]
fn test_new_inline_mut_panic() {
let too_long = "123456789012345678901234";
let _ = StrMut::new_inline(too_long);
}
#[test]
#[should_panic(expected = "index is not on a valid UTF-8 character boundary")]
fn test_insert_non_char_boundary_inline() {
let mut m = StrMut::new("世界");
m.insert(1, "x"); }
#[test]
#[should_panic(expected = "index is not on a valid UTF-8 character boundary")]
fn test_insert_non_char_boundary_heap() {
let mut m = StrMut::new("hello world hello world 世界");
m.insert(25, "x"); }
#[test]
fn test_try_into_mut_success_inline() {
let s = Str::new("hello");
let m = s.try_into_mut().unwrap();
assert_eq!(m.as_str(), "hello");
assert!(!m.is_spilled());
}
#[test]
fn test_try_into_mut_success_heap_unique() {
let s = Str::new("hello world hello world!!");
let m = s.try_into_mut().unwrap();
assert_eq!(m.as_str(), "hello world hello world!!");
assert!(m.is_spilled());
}
#[test]
fn test_try_into_mut_failure_shared() {
let s = Str::new("hello world hello world!!");
let s2 = s.clone();
let result = s.try_into_mut();
assert!(result.is_err());
let original = result.unwrap_err();
assert_eq!(original.as_str(), "hello world hello world!!");
assert_eq!(s2.as_str(), "hello world hello world!!");
}
#[test]
fn test_push_str_inline_to_heap() {
let mut m = StrMut::new("12345678901234567890"); assert!(!m.is_spilled());
m.push_str("1234"); assert!(m.is_spilled());
assert_eq!(m.as_str(), "123456789012345678901234");
}
#[test]
fn test_push_char_inline_to_heap() {
let mut m = StrMut::new("1234567890123456789012"); assert!(!m.is_spilled());
m.push('x');
assert!(!m.is_spilled());
m.push('y');
assert!(m.is_spilled()); assert_eq!(m.as_str(), "1234567890123456789012xy");
}
#[test]
fn test_push_multibyte_char_promotion() {
let mut m = StrMut::new("12345678901234567890"); assert!(!m.is_spilled());
m.push('🌍'); assert!(m.is_spilled()); assert_eq!(m.as_str(), "12345678901234567890🌍");
}
#[test]
fn test_reserve_triggers_promotion() {
let mut m = StrMut::new("hello"); assert!(!m.is_spilled());
m.reserve(20); assert!(m.is_spilled());
assert_eq!(m.as_str(), "hello");
m.push_str("12345678901234567890"); assert_eq!(m.as_str(), "hello12345678901234567890");
}
#[test]
fn test_sequential_operations_promotion() {
let mut m = StrMut::new("abc");
assert!(!m.is_spilled());
for _ in 0..5 {
m.push_str("1234"); }
assert!(!m.is_spilled());
m.push('x');
assert!(m.is_spilled()); }
#[test]
fn test_strip_prefix_none() {
let s = Str::new("hello world");
assert_eq!(s.strip_prefix("goodbye"), None);
assert_eq!(s.strip_prefix("hello world!"), None);
}
#[test]
fn test_strip_suffix_none() {
let s = Str::new("hello world");
assert_eq!(s.strip_suffix("goodbye"), None);
assert_eq!(s.strip_suffix("!hello world"), None);
}
#[test]
fn test_slice_ref_inline() {
let text = "hello world";
let s = Str::new(text);
assert!(!s.is_spilled());
let subset = &text[6..11]; let sliced = s.slice_ref(subset);
assert_eq!(sliced.as_str(), "world");
assert!(!sliced.is_spilled()); }
#[test]
fn test_trim_end_zero_copy_heap() {
let text = "hello world hello world hello ";
let s = Str::new(text);
assert!(s.is_spilled());
let trimmed = s.trim_end();
assert_eq!(trimmed.as_str(), "hello world hello world hello");
assert!(trimmed.is_spilled());
}
#[test]
fn test_split_iterator_complete() {
let s = Str::new("a,b,c,d,e");
let parts: Vec<_> = s.split(",").collect();
assert_eq!(parts.len(), 5);
assert_eq!(parts[0], "a");
assert_eq!(parts[1], "b");
assert_eq!(parts[4], "e");
let s2 = Str::new("a,,b");
let parts2: Vec<_> = s2.split(",").collect();
assert_eq!(parts2.len(), 3);
assert_eq!(parts2[1], ""); }
#[test]
fn test_split_no_separator() {
let s = Str::new("hello");
let parts: Vec<_> = s.split(",").collect();
assert_eq!(parts.len(), 1);
assert_eq!(parts[0], "hello");
}
#[test]
fn test_is_unique_inline() {
let s = Str::new("hello");
assert!(s.is_unique()); }
#[test]
fn test_is_unique_heap_unshared() {
let s = Str::new("hello world hello world!!");
assert!(s.is_unique()); }
#[test]
fn test_is_unique_heap_shared() {
let s1 = Str::new("hello world hello world!!");
let s2 = s1.clone();
assert!(!s1.is_unique()); assert!(!s2.is_unique());
}
#[test]
fn test_into_ascii_uppercase_leaves_shared_clone_untouched() {
let s1 = Str::new("hello world hello world!!");
let s2 = s1.clone();
let upper = s1.into_ascii_uppercase();
assert_eq!(upper.as_str(), "HELLO WORLD HELLO WORLD!!");
assert_eq!(s2.as_str(), "hello world hello world!!"); }
#[test]
fn test_promote_inline_to_heap() {
let mut s = Str::new("hello");
assert!(!s.is_spilled());
let heap_str = s.promote();
assert_eq!(&**heap_str, "hello");
assert!(s.is_spilled());
}
#[test]
fn test_from_arc_str() {
let arc: std::sync::Arc<str> = "hello world hello world".into();
let s = Str::from(arc);
assert!(s.is_spilled());
assert_eq!(s.as_str(), "hello world hello world");
}
#[test]
fn test_from_box_str() {
let boxed: Box<str> = "hello world".into();
let s = Str::from(boxed);
assert_eq!(s.as_str(), "hello world");
}
#[test]
fn test_from_cow_borrowed() {
let cow = std::borrow::Cow::Borrowed("hello");
let s = Str::from(cow);
assert_eq!(s.as_str(), "hello");
}
#[test]
fn test_from_cow_owned() {
let cow = std::borrow::Cow::<str>::Owned(String::from("hello world hello world"));
let s = Str::from(cow);
assert_eq!(s.as_str(), "hello world hello world");
}
#[test]
fn test_into_string() {
let s = Str::new("hello world");
let string: String = s.into();
assert_eq!(string, "hello world");
}
#[test]
fn test_into_bytes() {
let s = Str::new("hello world hello world");
let bytes: bytes::Bytes = s.into();
assert_eq!(&bytes[..], b"hello world hello world");
}
#[test]
fn test_into_bytes_inline() {
let s = Str::new("hello");
let bytes: bytes::Bytes = s.into();
assert_eq!(&bytes[..], b"hello");
}
#[test]
fn test_from_iterator_char() {
let chars = vec!['h', 'e', 'l', 'l', 'o'];
let s: Str = chars.into_iter().collect();
assert_eq!(s.as_str(), "hello");
}
#[test]
fn test_from_iterator_str_ref() {
let strs = vec!["hello", " ", "world"];
let s: Str = strs.into_iter().collect();
assert_eq!(s.as_str(), "hello world");
}
#[test]
fn test_from_iterator_string() {
let strings = vec![String::from("hello"), String::from(" "), String::from("world")];
let s: Str = strings.into_iter().collect();
assert_eq!(s.as_str(), "hello world");
}
#[test]
fn test_from_iterator_triggers_heap() {
let s: Str = "123456789012345678901234".chars().collect();
assert!(s.is_spilled());
assert_eq!(s.as_str(), "123456789012345678901234");
}
#[test]
fn test_to_ascii_lowercase_str() {
let s = "HELLO WORLD";
let lower = s.to_ascii_lowercase_str();
assert_eq!(lower.as_str(), "hello world");
assert!(!lower.is_spilled());
let long = "HELLO WORLD HELLO WORLD!!";
let lower_long = long.to_ascii_lowercase_str();
assert_eq!(lower_long.as_str(), "hello world hello world!!");
assert!(lower_long.is_spilled());
}
#[test]
fn test_to_ascii_uppercase_str() {
let s = "hello world";
let upper = s.to_ascii_uppercase_str();
assert_eq!(upper.as_str(), "HELLO WORLD");
let already_upper = "HELLO";
let upper2 = already_upper.to_ascii_uppercase_str();
assert_eq!(upper2.as_str(), "HELLO");
}
#[test]
fn test_into_ascii_lowercase() {
let s = Str::new("HELLO");
let lower = s.into_ascii_lowercase();
assert_eq!(lower.as_str(), "hello");
}
#[test]
fn test_into_ascii_uppercase() {
let s = Str::new("hello");
let upper = s.into_ascii_uppercase();
assert_eq!(upper.as_str(), "HELLO");
}
#[test]
fn test_fmts_basic() {
let s = fmts!("hello {}", "world");
assert_eq!(s.as_str(), "hello world");
}
#[test]
fn test_fmts_numbers() {
let s = fmts!("count: {}, pi: {:.2}", 42, std::f64::consts::PI);
assert_eq!(s.as_str(), "count: 42, pi: 3.14");
}
#[test]
fn test_fmts_no_args() {
let s = fmts!("static text");
assert_eq!(s.as_str(), "static text");
}
#[test]
fn test_fmts_mut_basic() {
let mut s = fmts_mut!("hello {}", "world");
assert_eq!(s.as_str(), "hello world");
s.push('!');
assert_eq!(s.as_str(), "hello world!");
}
#[test]
fn test_fmts_heap_allocation() {
let s = fmts!("{}", "123456789012345678901234");
assert!(s.is_spilled());
assert_eq!(s.as_str(), "123456789012345678901234");
}
#[test]
fn test_new_static() {
const STATIC: &str = "hello world hello world";
let s = Str::new_static(STATIC);
assert!(s.is_spilled());
assert_eq!(s.as_str(), STATIC);
const SHORT: &str = "hi";
let s2 = Str::new_static(SHORT);
assert!(s2.is_spilled()); }
#[test]
fn test_str_serde_roundtrip() {
let s = Str::new("hello world");
let json = json::to_string(&s).unwrap();
assert_eq!(json, r#""hello world""#);
let deserialized: Str = json::from_str(&json).unwrap();
assert_eq!(deserialized.as_str(), "hello world");
}
#[test]
fn test_str_serde_empty() {
let s = Str::new("");
let json = json::to_string(&s).unwrap();
assert_eq!(json, r#""""#);
let deserialized: Str = json::from_str(&json).unwrap();
assert!(deserialized.is_empty());
}
#[test]
fn test_str_serde_multibyte() {
let s = Str::new("世界 🌍");
let json = json::to_string(&s).unwrap();
let deserialized: Str = json::from_str(&json).unwrap();
assert_eq!(deserialized.as_str(), "世界 🌍");
}
#[test]
fn test_clone_inline() {
let s1 = Str::new("hello");
let s2 = s1.clone();
assert_eq!(s1, s2);
assert!(!s1.is_spilled());
assert!(!s2.is_spilled());
}
#[test]
fn test_clone_heap() {
let s1 = Str::new("hello world hello world!!");
let s2 = s1.clone();
assert_eq!(s1, s2);
assert!(!s1.is_unique());
assert!(!s2.is_unique());
}
#[test]
fn test_default_str() {
let s = Str::default();
assert!(s.is_empty());
assert!(!s.is_spilled());
}
#[test]
fn test_default_strmut() {
let m = StrMut::default();
assert!(m.is_empty());
assert!(!m.is_spilled());
}
#[test]
fn test_with_capacity_inline() {
let m = StrMut::with_capacity(10);
assert!(!m.is_spilled());
assert!(m.is_empty());
}
#[test]
fn test_with_capacity_heap() {
let m = StrMut::with_capacity(100);
assert!(m.is_spilled());
assert!(m.is_empty());
}
#[test]
fn test_freeze_inline() {
let m = StrMut::new("hello");
let s = m.freeze();
assert_eq!(s.as_str(), "hello");
assert!(!s.is_spilled());
}
#[test]
fn test_freeze_heap() {
let m = StrMut::new("hello world hello world!!");
let s = m.freeze();
assert_eq!(s.as_str(), "hello world hello world!!");
assert!(s.is_spilled());
}
#[test]
fn test_extend_empty_iterator() {
let mut m = StrMut::new("hello");
let empty: Vec<&str> = vec![];
m.extend(empty);
assert_eq!(m.as_str(), "hello");
}
#[test]
fn test_equality_different_repr() {
let inline = Str::new("hello");
let heap = Str::new("hello world hello world!!").slice(0..5);
assert_eq!(inline, heap);
assert!(!inline.is_spilled());
assert!(heap.is_spilled());
}
#[test]
fn test_ordering() {
let a = Str::new("apple");
let b = Str::new("banana");
let c = Str::new("cherry");
assert!(a < b);
assert!(b < c);
assert!(a < c);
assert!((b >= a));
}
#[test]
fn test_hash_consistency() {
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
let s1 = Str::new("hello world");
let s2 = Str::new("hello world hello world").slice(0..11);
let mut h1 = DefaultHasher::new();
s1.hash(&mut h1);
let mut h2 = DefaultHasher::new();
s2.hash(&mut h2);
assert_eq!(h1.finish(), h2.finish());
}
#[test]
fn test_as_bytes() {
let s = Str::new("hello 🌍");
let bytes = s.as_bytes();
assert_eq!(bytes, "hello 🌍".as_bytes());
}
#[test]
fn test_insert_at_end() {
let mut m = StrMut::new("hello");
m.insert(5, " world");
assert_eq!(m.as_str(), "hello world");
}
#[test]
fn test_insert_at_start() {
let mut m = StrMut::new("world");
m.insert(0, "hello ");
assert_eq!(m.as_str(), "hello world");
}
#[test]
fn test_truncate_noop() {
let mut s = Str::new("hello");
s.truncate(100); assert_eq!(s.as_str(), "hello");
s.truncate(5); assert_eq!(s.as_str(), "hello");
}
#[test]
fn test_truncate_mut_noop() {
let mut m = StrMut::new("hello");
m.truncate(100);
assert_eq!(m.as_str(), "hello");
}
#[test]
fn test_deref_coercion() {
let s = Str::new("hello");
let len = s.len(); assert_eq!(len, 5);
fn takes_str(s: &str) -> usize {
s.len()
}
assert_eq!(takes_str(&s), 5);
}
#[test]
fn test_borrow_trait() {
use std::borrow::Borrow;
let s = Str::new("hello");
let borrowed: &str = s.borrow();
assert_eq!(borrowed, "hello");
}
#[test]
fn test_as_ref_os_str() {
let s = Str::new("hello");
let os_str: &std::ffi::OsStr = s.as_ref();
assert_eq!(os_str, "hello");
}
#[test]
fn test_as_ref_path() {
let s = Str::new("/tmp/file.txt");
let path: &std::path::Path = s.as_ref();
assert_eq!(path.to_str().unwrap(), "/tmp/file.txt");
}
#[test]
fn test_partial_eq_str() {
let s = Str::new("hello");
assert_eq!(s, "hello");
assert_eq!("hello", s);
assert_ne!(s, "world");
}
#[test]
fn test_partial_eq_string() {
let s = Str::new("hello");
let string = String::from("hello");
assert_eq!(s, string);
assert_eq!(string, s);
}
#[test]
fn test_strmut_deref_mut() {
let mut m = StrMut::new("hello");
let str_mut: &mut str = &mut m;
str_mut.make_ascii_uppercase();
assert_eq!(m.as_str(), "HELLO");
}
#[test]
fn test_from_str_parse() {
let s: Str = "hello world".parse().unwrap();
assert_eq!(s.as_str(), "hello world");
}
#[test]
fn test_into_str_trait() {
use super::IntoStr;
let s: Str = "hello".into_str();
assert_eq!(s.as_str(), "hello");
let num: i32 = 42;
let s2 = (&num).to_str();
assert_eq!(s2.as_str(), "42");
}
#[test]
fn test_write_trait() {
use std::fmt::Write;
let mut m = StrMut::new("hello");
write!(&mut m, " {}", 42).unwrap();
assert_eq!(m.as_str(), "hello 42");
}
#[test]
fn test_display_format() {
let s = Str::new("hello");
let formatted = format!("{s}");
assert_eq!(formatted, "hello");
}
#[test]
fn test_debug_format() {
let s = Str::new("hello");
let formatted = format!("{s:?}");
assert_eq!(formatted, "\"hello\"");
}
#[test]
fn test_slice_heap_zero_copy() {
let s = Str::new("hello world hello world!!");
let sliced = s.slice(6..11);
assert_eq!(sliced.as_str(), "world");
assert!(sliced.is_spilled());
}
#[test]
fn test_slice_inline_creates_inline() {
let s = Str::new("hello world");
let sliced = s.slice(0..5);
assert_eq!(sliced.as_str(), "hello");
assert!(!sliced.is_spilled());
}
#[test]
fn test_reserve_heap_noop() {
let mut m = StrMut::new("hello world hello world!!");
assert!(m.is_spilled());
let initial_len = m.len();
m.reserve(10);
assert_eq!(m.len(), initial_len);
assert_eq!(m.as_str(), "hello world hello world!!");
}
#[test]
fn test_push_empty_string() {
let mut m = StrMut::new("hello");
m.push_str("");
assert_eq!(m.as_str(), "hello");
}
#[test]
fn test_split_empty_separator() {
let s = Str::new("hello");
let parts = s.split("").filter(|s| !s.is_empty()).count();
assert_eq!(parts, 5);
}
#[test]
fn test_from_bytes_mut() {
let bytes_mut = bytes::BytesMut::from("hello world hello world");
let result = StrMut::from_utf8_owned(bytes_mut);
assert!(result.is_ok());
let m = result.unwrap();
assert_eq!(m.as_str(), "hello world hello world");
assert!(m.is_spilled());
}
#[test]
fn test_into_bytes_mut() {
let m = StrMut::new("hello world hello world");
let bytes_mut: bytes::BytesMut = m.into();
assert_eq!(&bytes_mut[..], b"hello world hello world");
}
#[test]
fn test_str_to_str() {
let s = Str::new("hello");
let str_ref: bytes_utils::Str = s.into();
assert_eq!(&*str_ref, "hello");
}
#[test]
fn test_strmut_to_strmut() {
let m = StrMut::new("hello world hello world");
let str_mut: bytes_utils::StrMut = m.into();
assert_eq!(&*str_mut, "hello world hello world");
}
}