use alloc::fmt;
use alloc::vec::Vec;
use core::borrow::Borrow;
use core::error::Error;
use core::hash::Hash;
use core::hint::unreachable_unchecked;
use core::mem::{ManuallyDrop, MaybeUninit};
use core::ops::{Bound, Deref, DerefMut, Range, RangeBounds};
use core::ptr;
use raw::borrowed::Borrowed;
use raw::{Inline, Split, SplitMut, Tag, Union};
use self::raw::try_range_of;
pub use self::raw::HipByt;
use crate::Backend;
mod cmp;
mod convert;
mod raw;
#[cfg(feature = "borsh")]
mod borsh;
#[cfg(feature = "bstr")]
mod bstr;
#[cfg(feature = "serde")]
pub mod serde;
#[cfg(test)]
mod tests;
#[cfg(feature = "bstr")]
type Owned = ::bstr::BString;
#[cfg(not(feature = "bstr"))]
type Owned = Vec<u8>;
#[cfg(feature = "bstr")]
type Slice = ::bstr::BStr;
#[cfg(not(feature = "bstr"))]
type Slice = [u8];
impl<'borrow, B> HipByt<'borrow, B>
where
B: Backend,
{
#[inline]
#[must_use]
pub const fn new() -> Self {
Self::inline_empty()
}
#[must_use]
pub const fn inline(bytes: &[u8]) -> Self {
assert!(bytes.len() <= Self::inline_capacity(), "slice too large");
unsafe { Self::inline_unchecked(bytes) }
}
#[must_use]
pub const fn try_inline(bytes: &[u8]) -> Option<Self> {
if bytes.len() <= Self::inline_capacity() {
Some(unsafe { Self::inline_unchecked(bytes) })
} else {
None
}
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
if capacity <= Self::inline_capacity() {
Self::inline_empty()
} else {
Self::from_vec(Vec::with_capacity(capacity))
}
}
#[must_use]
#[inline]
pub const fn borrowed(bytes: &'borrow [u8]) -> Self {
Union {
borrowed: Borrowed::new(bytes),
}
.into_raw()
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
match self.split() {
Split::Inline(inline) => inline.len(),
Split::Allocated(heap) => heap.len(),
Split::Borrowed(borrowed) => borrowed.len(),
}
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
#[must_use]
pub const fn as_ptr(&self) -> *const u8 {
match self.split() {
Split::Inline(inline) => inline.as_ptr(),
Split::Allocated(heap) => heap.as_ptr(),
Split::Borrowed(borrowed) => borrowed.as_ptr(),
}
}
#[inline]
#[must_use]
pub fn as_mut_ptr(&mut self) -> Option<*mut u8> {
match self.split_mut() {
SplitMut::Inline(inline) => Some(inline.as_mut_ptr()),
SplitMut::Allocated(heap) => heap.as_mut_ptr(),
SplitMut::Borrowed(_) => None,
}
}
#[inline]
#[must_use]
pub unsafe fn as_mut_ptr_unchecked(&mut self) -> *mut u8 {
match self.split_mut() {
SplitMut::Inline(inline) => inline.as_mut_ptr(),
SplitMut::Allocated(heap) => unsafe { heap.as_mut_ptr_unchecked() },
SplitMut::Borrowed(_) => {
if cfg!(debug_assertions) {
panic!("mutable pointer of borrowed string");
} else {
unsafe {
unreachable_unchecked();
}
}
}
}
}
#[inline]
#[must_use]
pub const fn as_slice(&self) -> &[u8] {
match self.split() {
Split::Inline(inline) => inline.as_slice(),
Split::Allocated(heap) => heap.as_slice(),
Split::Borrowed(borrowed) => borrowed.as_slice(),
}
}
#[inline]
#[must_use]
pub fn as_mut_slice(&mut self) -> Option<&mut [u8]> {
match self.split_mut() {
SplitMut::Inline(inline) => Some(inline.as_mut_slice()),
SplitMut::Allocated(allocated) => allocated.as_mut_slice(),
SplitMut::Borrowed(_) => None,
}
}
#[inline]
pub unsafe fn as_mut_slice_unchecked(&mut self) -> &mut [u8] {
match self.split_mut() {
SplitMut::Inline(inline) => inline.as_mut_slice(),
SplitMut::Allocated(allocated) => unsafe { allocated.as_mut_slice_unchecked() },
SplitMut::Borrowed(_) => {
if cfg!(debug_assertions) {
panic!("mutable slice of borrowed string");
} else {
unsafe { unreachable_unchecked() }
}
}
}
}
#[inline]
#[doc(alias = "make_mut")]
pub fn to_mut_slice(&mut self) -> &mut [u8] {
self.make_unique();
unsafe { self.as_mut_slice_unchecked() }
}
#[inline]
#[must_use]
pub const fn is_inline(&self) -> bool {
matches!(self.tag(), Tag::Inline)
}
#[inline]
#[must_use]
pub const fn is_borrowed(&self) -> bool {
matches!(self.tag(), Tag::Borrowed)
}
pub const fn into_borrowed(self) -> Result<&'borrow [u8], Self> {
match self.split() {
Split::Allocated(_) | Split::Inline(_) => Err(self),
Split::Borrowed(borrowed) => {
let result = borrowed.as_slice();
core::mem::forget(self); Ok(result)
}
}
}
#[inline]
#[must_use]
pub const fn as_borrowed(&self) -> Option<&'borrow [u8]> {
match self.split() {
Split::Allocated(_) | Split::Inline(_) => None,
Split::Borrowed(borrowed) => Some(borrowed.as_slice()),
}
}
#[inline]
#[must_use]
pub const fn is_allocated(&self) -> bool {
matches!(self.tag(), Tag::Allocated)
}
#[inline]
#[must_use]
pub const fn is_normalized(&self) -> bool {
self.is_inline() || self.is_borrowed() || self.len() > Self::inline_capacity()
}
#[inline]
#[must_use]
pub const fn inline_capacity() -> usize {
Inline::capacity()
}
#[inline]
#[must_use]
pub fn capacity(&self) -> usize {
match self.split() {
Split::Inline(_) => Self::inline_capacity(),
Split::Borrowed(borrowed) => borrowed.len(), Split::Allocated(allocated) => allocated.capacity(),
}
}
#[inline]
#[allow(clippy::option_if_let_else)]
pub fn into_vec(self) -> Result<Vec<u8>, Self> {
let mut this = ManuallyDrop::new(self);
if let Some(allocated) = this.take_allocated() {
allocated
.try_into_vec()
.map_err(|allocated| Union { allocated }.into_raw())
} else {
Err(ManuallyDrop::into_inner(this))
}
}
#[inline]
#[must_use]
pub fn into_owned(self) -> HipByt<'static, B> {
let tag = self.tag();
let old = self.union_move();
unsafe {
match tag {
Tag::Allocated => HipByt::from_allocated(old.allocated),
Tag::Borrowed => HipByt::from_slice(old.borrowed.as_slice()),
Tag::Inline => HipByt::from_inline(old.inline),
}
}
}
#[must_use]
#[track_caller]
pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
match self.try_slice(range) {
Ok(result) => result,
Err(err) => panic!("{}", err),
}
}
pub fn try_slice(&self, range: impl RangeBounds<usize>) -> Result<Self, SliceError<B>> {
let range = simplify_range(range, self.len())
.map_err(|(start, end, kind)| SliceError::new(kind, start, end, self))?;
let slice = unsafe { self.range_unchecked(range) };
Ok(slice)
}
#[must_use]
pub unsafe fn slice_unchecked(&self, range: impl RangeBounds<usize>) -> Self {
let start = match range.start_bound() {
Bound::Excluded(&n) => n + 1,
Bound::Included(&n) => n,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Excluded(&n) => n,
Bound::Included(&n) => n + 1,
Bound::Unbounded => self.len(),
};
unsafe { self.range_unchecked(start..end) }
}
#[must_use]
#[track_caller]
pub fn slice_ref(&self, slice: &[u8]) -> Self {
let Some(result) = self.try_slice_ref(slice) else {
panic!("slice {slice:p} is not a part of {self:p}")
};
result
}
#[must_use]
pub fn try_slice_ref(&self, range: &[u8]) -> Option<Self> {
let slice = range;
let range = try_range_of(self.as_slice(), slice)?;
Some(unsafe { self.slice_unchecked(range) })
}
#[inline]
#[must_use]
pub fn mutate(&mut self) -> RefMut<'_, 'borrow, B> {
let owned = self.take_vec();
#[cfg(feature = "bstr")]
let owned = owned.into();
RefMut {
result: self,
owned,
}
}
#[inline]
pub fn clear(&mut self) {
self.truncate(0);
}
pub fn pop(&mut self) -> Option<u8> {
let len = self.len();
if len == 0 {
None
} else {
let result = unsafe { *self.as_slice().get_unchecked(len - 1) };
self.truncate(len - 1);
Some(result)
}
}
#[inline]
pub fn push(&mut self, value: u8) {
self.push_slice(&[value]);
}
#[inline]
#[doc(alias = "extend_from_slice", alias = "append")]
pub fn push_slice(&mut self, addition: &[u8]) {
let new_len = self.len() + addition.len();
if self.is_allocated() {
let allocated = unsafe { &mut self.union_mut().allocated };
if allocated.is_unique() {
unsafe {
allocated.push_slice_unchecked(addition);
}
return;
}
}
if new_len <= Self::inline_capacity() {
if !self.is_inline() {
*self = unsafe { Self::inline_unchecked(self.as_slice()) };
}
unsafe {
self.union_mut().inline.push_slice_unchecked(addition);
}
return;
}
let mut vec = Vec::with_capacity(new_len);
vec.extend_from_slice(self.as_slice());
vec.extend_from_slice(addition);
*self = Self::from_vec(vec);
}
#[must_use]
pub fn repeat(&self, n: usize) -> Self {
if self.is_empty() || n == 1 {
return self.clone();
}
let src_len = self.len();
let new_len = src_len.checked_mul(n).expect("capacity overflow");
if new_len <= Self::inline_capacity() {
let mut inline = Inline::zeroed(new_len);
let src = self.as_slice().as_ptr();
let mut dst = inline.as_mut_slice().as_mut_ptr();
unsafe {
for _ in 0..n {
ptr::copy_nonoverlapping(src, dst, src_len);
dst = dst.add(src_len);
}
}
Self::from_inline(inline)
} else {
let vec = self.as_slice().repeat(n);
Self::from_vec(vec)
}
}
#[inline]
pub fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit<u8>] {
match self.split_mut() {
SplitMut::Borrowed(_) => &mut [],
SplitMut::Inline(inline) => inline.spare_capacity_mut(),
SplitMut::Allocated(allocated) => allocated.spare_capacity_mut(),
}
}
pub unsafe fn set_len(&mut self, new_len: usize) {
match self.split_mut() {
SplitMut::Borrowed(borrowed) => unsafe {
borrowed.set_len(new_len);
},
SplitMut::Inline(inline) => unsafe { inline.set_len(new_len) },
SplitMut::Allocated(allocated) => unsafe { allocated.set_len(new_len) },
}
}
#[inline]
pub fn truncate(&mut self, new_len: usize) {
if new_len < self.len() {
if self.is_allocated() && new_len <= Self::inline_capacity() {
let new =
unsafe { Self::inline_unchecked(self.as_slice().get_unchecked(..new_len)) };
*self = new;
} else {
unsafe { self.set_len(new_len) }
}
}
debug_assert!(self.is_normalized());
}
pub fn shrink_to(&mut self, min_capacity: usize) {
if self.is_allocated() {
let min_capacity = min_capacity.max(self.len());
if min_capacity > Self::inline_capacity() {
let allocated = unsafe { &mut self.union_mut().allocated };
allocated.shrink_to(min_capacity);
} else {
let new = unsafe { Self::inline_unchecked(self.as_slice()) };
*self = new;
}
}
}
pub fn shrink_to_fit(&mut self) {
self.shrink_to(self.len());
}
#[inline]
#[must_use]
pub fn to_ascii_lowercase(&self) -> Self {
let mut other = self.clone();
other.to_mut_slice().make_ascii_lowercase();
other
}
#[inline]
pub fn make_ascii_lowercase(&mut self) {
self.to_mut_slice().make_ascii_lowercase();
}
#[inline]
#[must_use]
pub fn to_ascii_uppercase(&self) -> Self {
let mut other = self.clone();
other.to_mut_slice().make_ascii_uppercase();
other
}
#[inline]
pub fn make_ascii_uppercase(&mut self) {
self.to_mut_slice().make_ascii_uppercase();
}
#[must_use]
pub fn concat_slices(slices: &[&[u8]]) -> Self {
let new_len = slices.iter().map(|e| e.len()).sum();
if new_len == 0 {
return Self::new();
}
let mut new = Self::with_capacity(new_len);
let dst = new.spare_capacity_mut();
let dst_ptr = dst.as_mut_ptr().cast();
let final_ptr = slices.iter().fold(dst_ptr, |dst_ptr, slice| {
let len = slice.len();
unsafe {
ptr::copy_nonoverlapping(slice.as_ptr(), dst_ptr, len);
dst_ptr.add(len)
}
});
debug_assert_eq!(
{
#[expect(clippy::cast_sign_loss)]
let diff_u = unsafe { final_ptr.offset_from(dst_ptr) } as usize;
diff_u
},
new_len
);
unsafe { new.set_len(new_len) };
debug_assert_eq!(final_ptr.cast_const(), new.as_slice().as_ptr_range().end);
new
}
#[must_use]
pub fn concat<E, I>(slices: I) -> Self
where
E: AsRef<[u8]>,
I: IntoIterator<Item = E>,
I::IntoIter: Clone,
{
let slices = slices.into_iter();
let new_len = slices.clone().map(|e| e.as_ref().len()).sum();
if new_len == 0 {
return Self::new();
}
let mut new = Self::with_capacity(new_len);
let dst = new.spare_capacity_mut();
let dst_ptr: *mut u8 = dst.as_mut_ptr().cast();
let final_ptr = unsafe { dst_ptr.add(new_len) };
let _ = slices.fold(dst_ptr, |dst_ptr, slice| {
let slice = slice.as_ref();
let len = slice.len();
let end_ptr = unsafe { dst_ptr.add(len) };
assert!(end_ptr <= final_ptr, "slices changed during concat");
unsafe {
ptr::copy_nonoverlapping(slice.as_ptr(), dst_ptr, len);
end_ptr
}
});
unsafe { new.set_len(new_len) };
debug_assert_eq!(final_ptr.cast_const(), new.as_slice().as_ptr_range().end);
new
}
#[must_use]
pub fn join_slices(slices: &[&[u8]], sep: impl AsRef<[u8]>) -> Self {
let slices_len = slices.len();
if slices_len == 0 {
return Self::new();
}
let sep = sep.as_ref();
let sep_len = sep.len();
let slices_sum: usize = slices.iter().copied().map(<[_]>::len).sum();
let new_len = (slices_len - 1) * sep_len + slices_sum;
if new_len == 0 {
return Self::new();
}
let mut new = Self::with_capacity(new_len);
let dst = new.spare_capacity_mut();
let dst_ptr: *mut u8 = dst.as_mut_ptr().cast();
let final_ptr = unsafe { dst_ptr.add(new_len) };
let mut iter = slices.iter().copied();
let slice = unsafe { iter.next().unwrap_unchecked() };
let len = slice.len();
let end_ptr = unsafe { dst_ptr.add(len) };
debug_assert!(end_ptr <= final_ptr, "slices changed during concat");
unsafe {
ptr::copy_nonoverlapping(slice.as_ptr(), dst_ptr, len);
}
let _ = iter.fold(end_ptr, |mut dst_ptr, slice| {
let end_ptr = unsafe { dst_ptr.add(sep_len) };
debug_assert!(end_ptr <= final_ptr, "slices changed during concat");
unsafe {
ptr::copy_nonoverlapping(sep.as_ptr(), dst_ptr, sep_len);
}
dst_ptr = end_ptr;
let len = slice.len();
let end_ptr = unsafe { dst_ptr.add(len) };
debug_assert!(end_ptr <= final_ptr, "slices changed during concat");
unsafe {
ptr::copy_nonoverlapping(slice.as_ptr(), dst_ptr, len);
}
end_ptr
});
unsafe { new.set_len(new_len) };
debug_assert_eq!(final_ptr.cast_const(), new.as_slice().as_ptr_range().end);
new
}
#[must_use]
pub fn join<E, I>(slices: I, sep: impl AsRef<[u8]>) -> Self
where
E: AsRef<[u8]>,
I: IntoIterator<Item = E>,
I::IntoIter: Clone,
{
let mut iter = slices.into_iter();
let (segments, segments_len) = iter.clone().fold((0, 0), |(count, length), e| {
(count + 1, length + e.as_ref().len())
});
if segments == 0 {
return Self::new();
}
let sep = sep.as_ref();
let sep_len = sep.len();
let new_len = (segments - 1) * sep_len + segments_len;
let mut new = Self::with_capacity(new_len);
let dst = new.spare_capacity_mut();
let dst_ptr: *mut u8 = dst.as_mut_ptr().cast();
let final_ptr = unsafe { dst_ptr.add(new_len) };
if let Some(first) = iter.next() {
let first = first.as_ref();
let len = first.len();
let end_ptr = unsafe { dst_ptr.add(first.len()) };
assert!(end_ptr <= final_ptr, "slices changed during concat");
unsafe {
ptr::copy_nonoverlapping(first.as_ptr(), dst_ptr, len);
}
let _ = iter.fold(end_ptr, |mut dst_ptr, slice| {
let end_ptr = unsafe { dst_ptr.add(sep_len) };
assert!(end_ptr <= final_ptr, "slices changed during concat");
unsafe {
ptr::copy_nonoverlapping(sep.as_ptr(), dst_ptr, sep_len);
}
dst_ptr = end_ptr;
let slice = slice.as_ref();
let len = slice.len();
let end_ptr = unsafe { dst_ptr.add(len) };
assert!(end_ptr <= final_ptr, "slices changed during concat");
unsafe {
ptr::copy_nonoverlapping(slice.as_ptr(), dst_ptr, len);
}
end_ptr
});
}
unsafe { new.set_len(new_len) };
debug_assert_eq!(final_ptr.cast_const(), new.as_slice().as_ptr_range().end);
new
}
}
impl<B> HipByt<'static, B>
where
B: Backend,
{
#[inline]
#[must_use]
pub const fn from_static(bytes: &'static [u8]) -> Self {
Self::borrowed(bytes)
}
}
impl<B> Default for HipByt<'_, B>
where
B: Backend,
{
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<B> Deref for HipByt<'_, B>
where
B: Backend,
{
type Target = Slice;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<B> Borrow<[u8]> for HipByt<'_, B>
where
B: Backend,
{
#[inline]
fn borrow(&self) -> &[u8] {
self.as_slice()
}
}
impl<B> Hash for HipByt<'_, B>
where
B: Backend,
{
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_slice().hash(state);
}
}
impl<B> fmt::Debug for HipByt<'_, B>
where
B: Backend,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_slice().fmt(f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SliceErrorKind {
StartGreaterThanEnd,
StartOutOfBounds,
EndOutOfBounds,
}
pub(crate) fn simplify_range(
range: impl RangeBounds<usize>,
len: usize,
) -> Result<Range<usize>, (usize, usize, SliceErrorKind)> {
simplify_range_mono(
range.start_bound().cloned(),
range.end_bound().cloned(),
len,
)
}
const fn simplify_range_mono(
start: Bound<usize>,
end: Bound<usize>,
len: usize,
) -> Result<Range<usize>, (usize, usize, SliceErrorKind)> {
let start = match start {
Bound::Included(start) => start,
Bound::Excluded(start) => start + 1,
Bound::Unbounded => 0,
};
let end = match end {
Bound::Included(end) => end + 1,
Bound::Excluded(end) => end,
Bound::Unbounded => len,
};
if start > len {
Err((start, end, SliceErrorKind::StartOutOfBounds))
} else if end > len {
Err((start, end, SliceErrorKind::EndOutOfBounds))
} else if start > end {
Err((start, end, SliceErrorKind::StartGreaterThanEnd))
} else {
Ok(Range { start, end })
}
}
pub struct SliceError<'a, 'borrow, B>
where
B: Backend,
{
kind: SliceErrorKind,
start: usize,
end: usize,
bytes: &'a HipByt<'borrow, B>,
}
impl<B> Clone for SliceError<'_, '_, B>
where
B: Backend,
{
fn clone(&self) -> Self {
*self
}
}
impl<B> Copy for SliceError<'_, '_, B> where B: Backend {}
impl<B> Eq for SliceError<'_, '_, B> where B: Backend {}
impl<B> PartialEq for SliceError<'_, '_, B>
where
B: Backend,
{
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
&& self.start == other.start
&& self.end == other.end
&& self.bytes == other.bytes
}
}
impl<'a, B> SliceError<'_, 'a, B>
where
B: Backend,
{
const fn new(kind: SliceErrorKind, start: usize, end: usize, bytes: &'a HipByt<B>) -> Self {
Self {
kind,
start,
end,
bytes,
}
}
#[inline]
#[must_use]
pub const fn kind(&self) -> SliceErrorKind {
self.kind
}
#[inline]
#[must_use]
pub const fn start(&self) -> usize {
self.start
}
#[inline]
#[must_use]
pub const fn end(&self) -> usize {
self.end
}
#[inline]
#[must_use]
pub const fn range(&self) -> Range<usize> {
self.start..self.end
}
#[inline]
#[must_use]
pub const fn source(&self) -> &HipByt<B> {
self.bytes
}
}
impl<B> fmt::Debug for SliceError<'_, '_, B>
where
B: Backend,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SliceError")
.field("kind", &self.kind)
.field("start", &self.start)
.field("end", &self.end)
.field("bytes", &self.bytes)
.finish()
}
}
impl<B> fmt::Display for SliceError<'_, '_, B>
where
B: Backend,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
SliceErrorKind::StartGreaterThanEnd => {
write!(f, "range starts at {} but ends at {}", self.start, self.end)
}
SliceErrorKind::StartOutOfBounds => write!(
f,
"range start index {} out of bounds for slice of length {}",
self.start,
self.bytes.len()
),
SliceErrorKind::EndOutOfBounds => {
write!(
f,
"range end index {} out of bounds for slice of length {}",
self.end,
self.bytes.len()
)
}
}
}
}
impl<B> Error for SliceError<'_, '_, B> where B: Backend {}
pub struct RefMut<'a, 'borrow, B>
where
B: Backend,
{
result: &'a mut HipByt<'borrow, B>,
owned: Owned,
}
impl<B> Drop for RefMut<'_, '_, B>
where
B: Backend,
{
fn drop(&mut self) {
let owned = core::mem::take(&mut self.owned);
*self.result = HipByt::from(owned);
}
}
impl<B> Deref for RefMut<'_, '_, B>
where
B: Backend,
{
type Target = Owned;
fn deref(&self) -> &Self::Target {
&self.owned
}
}
impl<B> DerefMut for RefMut<'_, '_, B>
where
B: Backend,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.owned
}
}