#![cfg_attr(not(feature = "alloc"), doc = "
# WARNING
The `alloc` feature is disabled. This means that a `TinyString` won't be able to
grow over it's stack capacity.
The following functions from [TinyString] can cause the program to panic if the string
exceeds its capacity.
- [with_capacity]
- [repeat](TinyString::repeat)
- [push]
- [push_str]
- [reserve]
- [reserve_exact]
- [extend_from_within](TinyString::extend_from_within)
- [insert](TinyString::insert)
- [insert_str](TinyString::insert_str)
## Alternatives
| May Panic | No Panic |
| --------- | -------- |
| [push] | [push_within_capacity](TinyString::push_within_capacity) |
| [push_str] | [push_within_capacity](TinyString::push_str_within_capacity) |
| [reserve] | [try_reserve](TinyString::try_reserve) |
| [with_capacity] | [try_with_capacity](TinyString::try_with_capacity) |
| [reserve] | [try_reserve](TinyString::try_reserve) |
| [reserve_exact] | [try_reserve_exact](TinyString::try_reserve_exact) |
[push]: TinyString::push
[push_str]: TinyString::push_str
[reserve]: TinyString::reserve
[reserve_exact]: TinyString::reserve_exact
[with_capacity]: TinyString::with_capacity
")]
#![cfg_attr(not(feature = "alloc"), doc = "
[String]: <https://doc.rust-lang.org/alloc/string/struct.String.html>
[Vec]: <https://doc.rust-lang.org/alloc/vec/struct.Vec.html>")]
#![no_std]
#![cfg_attr(feature = "use-nightly-features", feature(extend_one))]
use core::fmt::{self, Display};
use core::hash::Hash;
use core::ops::{AddAssign, Bound, Deref, DerefMut, Range, RangeBounds};
use core::str::{self, FromStr, Utf8Error};
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{
vec::Vec,
boxed::Box,
string::String,
};
use tiny_vec::TinyVec;
pub use tiny_vec::ResizeError;
pub mod iter;
pub mod drain;
mod cow;
pub use cow::Cow;
const MAX_N_STACK_ELEMENTS: usize = tiny_vec::n_elements_for_stack::<u8>();
pub struct TinyString<const N: usize = MAX_N_STACK_ELEMENTS> {
buf: TinyVec<u8, N>,
}
impl<const N: usize> TinyString<N> {
fn slice_range<R>(&self, range: R, len: usize) -> Range<usize>
where
R: RangeBounds<usize>
{
let start = match range.start_bound() {
Bound::Included(n) => *n,
Bound::Excluded(n) => *n + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(n) => *n + 1,
Bound::Excluded(n) => *n,
Bound::Unbounded => len,
};
assert!(start <= end);
assert!(end <= len);
assert!(self.is_char_boundary(start));
assert!(self.is_char_boundary(end));
Range { start, end }
}
}
impl<const N: usize> TinyString<N> {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self { buf: TinyVec::new() }
}
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self { buf: TinyVec::with_capacity(cap) }
}
pub fn try_with_capacity(cap: usize) -> Result<Self,ResizeError> {
Ok(Self { buf: TinyVec::try_with_capacity(cap)? })
}
pub fn from_utf8(utf8: TinyVec<u8, N>) -> Result<Self,Utf8Error> {
str::from_utf8(utf8.as_slice())?;
Ok(Self { buf: utf8 })
}
#[inline(always)]
#[must_use]
pub const unsafe fn from_utf8_unchecked(utf8: TinyVec<u8, N>) -> Self {
Self { buf: utf8 }
}
#[must_use]
pub fn repeat(slice: &str, n: usize) -> Self {
Self {
buf: TinyVec::repeat(slice.as_bytes(), n)
}
}
#[inline]
pub const fn len(&self) -> usize { self.buf.len() }
#[inline]
pub const fn is_empty(&self) -> bool { self.buf.is_empty() }
#[inline]
pub const fn capacity(&self) -> usize { self.buf.capacity() }
#[inline]
pub const fn lives_on_stack(&self) -> bool { self.buf.lives_on_stack() }
#[inline]
pub const fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.buf.as_slice()) }
}
#[inline]
pub const fn as_mut_str(&mut self) -> &mut str {
unsafe { str::from_utf8_unchecked_mut(self.buf.as_mut_slice()) }
}
#[inline]
pub const fn as_ptr(&self) -> *const u8 {
self.buf.as_ptr()
}
#[inline]
pub const fn as_mut_ptr(&mut self) -> *mut u8 {
self.buf.as_mut_ptr()
}
#[inline]
pub const fn as_bytes(&self) -> &[u8] {
self.buf.as_slice()
}
#[inline]
pub const unsafe fn as_mut_bytes(&mut self) -> &mut [u8] {
self.buf.as_mut_slice()
}
#[inline]
pub const unsafe fn as_mut_vec(&mut self) -> &mut TinyVec<u8, N> {
&mut self.buf
}
pub fn push(&mut self, c: char) {
let len = c.len_utf8();
if len == 1 {
self.buf.push(c as u8);
} else {
let mut buf = [0_u8; 4];
c.encode_utf8(&mut buf);
self.buf.copy_from_slice(&buf[..len]);
}
}
pub fn push_within_capacity(&mut self, c: char) -> Result<(), char> {
let len = c.len_utf8();
if self.buf.len() + len > self.buf.capacity() {
return Err(c)
}
if len == 1 {
unsafe { self.buf.push_unchecked(c as u8) };
} else {
let mut buf = [0_u8; 4];
c.encode_utf8(&mut buf);
self.buf.copy_from_slice(&buf[..len]);
}
Ok(())
}
pub fn pop(&mut self) -> Option<char> {
let c = self.chars().next_back()?;
let new_len = self.len() - c.len_utf8();
unsafe {
self.buf.set_len(new_len);
}
Some(c)
}
#[inline]
pub fn push_str(&mut self, s: &str) {
self.buf.copy_from_slice(s.as_bytes());
}
pub fn push_str_within_capacity<'a>(&mut self, s: &'a str) -> Result<(), &'a str> {
if self.buf.len() + s.len() > self.buf.capacity() {
Err(s)
} else {
self.buf.copy_from_slice(s.as_bytes());
Ok(())
}
}
#[inline]
#[cfg(feature = "alloc")]
pub fn shrink_to(&mut self, min_capacity: usize) {
self.buf.shrink_to(min_capacity)
}
#[inline]
#[cfg(feature = "alloc")]
pub fn shrink_to_fit(&mut self) {
self.buf.shrink_to_fit();
}
#[inline]
pub fn clear(&mut self) {
self.buf.clear();
}
#[inline]
pub fn reserve(&mut self, n: usize) {
self.buf.reserve(n);
}
#[inline]
pub fn try_reserve(&mut self, n: usize) -> Result<(), ResizeError> {
self.buf.try_reserve(n)
}
#[inline]
pub fn reserve_exact(&mut self, n: usize) {
self.buf.reserve_exact(n);
}
#[inline]
pub fn try_reserve_exact(&mut self, n: usize) -> Result<(), ResizeError> {
self.buf.try_reserve_exact(n)
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn into_boxed_str(self) -> Box<str> {
let b = self.buf.into_boxed_slice();
unsafe { alloc::str::from_boxed_utf8_unchecked(b) }
}
pub fn extend_from_within<R>(&mut self, range: R)
where
R: RangeBounds<usize>
{
let Range { start, end } = self.slice_range(range, self.len());
self.buf.extend_from_within_copied(start..end);
}
#[cfg(feature = "alloc")]
pub fn leak<'a>(mut self) -> &'a mut str {
self.buf.move_to_heap_exact();
self.buf.shrink_to_fit_heap_only();
unsafe {
let bytes = self.buf.leak();
str::from_utf8_unchecked_mut(bytes)
}
}
#[inline]
#[must_use = "use `.truncate()` if you don't need the other half"]
pub fn split_off(&mut self, at: usize) -> TinyString<N> {
assert!(self.is_char_boundary(at));
let other = self.buf.split_off(at);
unsafe { TinyString::from_utf8_unchecked(other) }
}
pub fn truncate(&mut self, new_len: usize) {
assert!(self.is_char_boundary(new_len));
self.buf.truncate(new_len);
}
pub fn insert(&mut self, index: usize, ch: char) {
assert!(self.is_char_boundary(index));
let mut buf = [0; 4];
ch.encode_utf8(&mut buf);
let len = ch.len_utf8();
self.buf.insert_slice(index, &buf[..len]).unwrap_or_else(|_| {
unreachable!("We've checked the index in the assertion above")
})
}
pub fn insert_str(&mut self, index: usize, s: &str) {
assert!(self.is_char_boundary(index));
self.buf.insert_slice(index, s.as_bytes()).unwrap_or_else(|_| {
unreachable!("We've checked the index in the assertion above")
})
}
}
impl<const N: usize> Default for TinyString<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> Deref for TinyString<N> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl<const N: usize> DerefMut for TinyString<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_str()
}
}
impl<const N: usize> From<&str> for TinyString<N> {
fn from(value: &str) -> Self {
let mut s = Self::with_capacity(value.len());
s.push_str(value);
s
}
}
impl<const N: usize> TryFrom<&[u8]> for TinyString<N> {
type Error = Utf8Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
str::from_utf8(value)?;
Ok(unsafe { Self::from_utf8_unchecked(TinyVec::from_slice_copied(value)) })
}
}
impl<const N: usize> TryFrom<TinyVec<u8, N>> for TinyString<N> {
type Error = Utf8Error;
fn try_from(value: TinyVec<u8, N>) -> Result<Self, Self::Error> {
Self::from_utf8(value)
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> TryFrom<Vec<u8>> for TinyString<N> {
type Error = Utf8Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
str::from_utf8(value.as_slice())?;
Ok(unsafe { Self::from_utf8_unchecked(TinyVec::from_vec(value)) })
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> From<String> for TinyString<N> {
fn from(value: String) -> Self {
let vec = Vec::from(value);
let vec = TinyVec::<_, N>::from_vec(vec);
unsafe { Self::from_utf8_unchecked(vec) }
}
}
impl<const N: usize> From<TinyString<N>> for TinyVec<u8, N> {
fn from(value: TinyString<N>) -> Self {
value.buf
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> From<TinyString<N>> for Vec<u8> {
fn from(value: TinyString<N>) -> Self {
value.buf.into_vec()
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> From<TinyString<N>> for Box<str> {
fn from(value: TinyString<N>) -> Self {
value.into_boxed_str()
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> From<Box<str>> for TinyString<N> {
fn from(value: Box<str>) -> Self {
let vec = value.as_bytes();
let s = TinyVec::from(vec);
unsafe { Self::from_utf8_unchecked(s) }
}
}
macro_rules! impl_from_iter {
($( $( { $($tok:tt)* } )? $t:ty),* $(,)?) => {
$(
impl< $($($tok)*, )? const N: usize> FromIterator<$t> for TinyString<N> {
fn from_iter<T: IntoIterator<Item = $t>>(iter: T) -> Self {
let mut s = Self::new();
s.extend(iter);
s
}
}
)*
};
}
impl_from_iter!(
char,
{'a} &'a char,
{'a} &'a str,
{'a, const M: usize} Cow<'a, M>
);
#[cfg(feature = "alloc")]
impl_from_iter!(Box<str>);
impl<const N: usize> Extend<char> for TinyString<N> {
fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
self.reserve(lower);
for c in iter {
self.push(c);
}
}
#[cfg(feature = "use-nightly-features")]
#[inline]
fn extend_one(&mut self, item: char) {
self.push(item);
}
#[cfg(feature = "use-nightly-features")]
#[inline]
fn extend_reserve(&mut self, additional: usize) {
self.reserve(additional);
}
}
impl<'a, const N: usize> Extend<&'a char> for TinyString<N> {
fn extend<T: IntoIterator<Item = &'a char>>(&mut self, iter: T) {
iter.into_iter().for_each(|slice| self.push(*slice));
}
#[cfg(feature = "use-nightly-features")]
#[inline]
fn extend_one(&mut self, item: &'a char) {
self.push(*item);
}
#[cfg(feature = "use-nightly-features")]
#[inline]
fn extend_reserve(&mut self, additional: usize) {
self.reserve(additional);
}
}
impl<'a, const N: usize> Extend<&'a str> for TinyString<N> {
fn extend<T: IntoIterator<Item = &'a str>>(&mut self, iter: T) {
iter.into_iter().for_each(|slice| self.push_str(slice));
}
#[cfg(feature = "use-nightly-features")]
#[inline]
fn extend_one(&mut self, item: &'a str) {
self.push_str(item);
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> Extend<Box<str>> for TinyString<N> {
fn extend<T: IntoIterator<Item = Box<str>>>(&mut self, iter: T) {
iter.into_iter().for_each(|slice| self.push_str(&slice));
}
#[cfg(feature = "use-nightly-features")]
#[inline]
fn extend_one(&mut self, item: Box<str>) {
self.push_str(&item);
}
}
impl<'a, const N: usize, const M: usize> Extend<Cow<'a, M>> for TinyString<N> {
fn extend<T: IntoIterator<Item = Cow<'a, M>>>(&mut self, iter: T) {
iter.into_iter().for_each(|slice| self.push_str(&slice));
}
#[cfg(feature = "use-nightly-features")]
#[inline]
fn extend_one(&mut self, item: Cow<'a, M>) {
self.push_str(&item);
}
}
impl<const N: usize, const M: usize> PartialEq<TinyString<M>> for TinyString<N> {
fn eq(&self, other: &TinyString<M>) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl<const N: usize> Eq for TinyString<N> { }
impl<'a, const N: usize, const M: usize> PartialEq<Cow<'a, M>> for TinyString<N> {
fn eq(&self, other: &Cow<'a, M>) -> bool {
self.as_bytes() == other.as_bytes()
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> PartialEq<String> for TinyString<N> {
fn eq(&self, other: &String) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl<const N: usize, const M: usize> PartialEq<TinyVec<u8, M>> for TinyString<N> {
fn eq(&self, other: &TinyVec<u8, M>) -> bool {
self.as_bytes() == other.as_slice()
}
}
impl<'a, const N: usize, const M: usize> PartialEq<tiny_vec::Cow<'a, u8, M>> for TinyString<N> {
fn eq(&self, other: &tiny_vec::Cow<'a, u8, M>) -> bool {
self.as_bytes() == other.as_slice()
}
}
#[cfg(feature = "alloc")]
impl<const N: usize> PartialEq<Vec<u8>> for TinyString<N> {
fn eq(&self, other: &Vec<u8>) -> bool {
self.as_bytes() == other.as_slice()
}
}
impl<const N: usize> PartialEq<str> for TinyString<N> {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<'a, const N: usize> PartialEq<&'a str> for TinyString<N> {
fn eq(&self, other: &&'a str) -> bool {
self.as_str() == *other
}
}
impl<const N: usize> PartialEq<[u8]> for TinyString<N> {
fn eq(&self, other: &[u8]) -> bool {
self.as_bytes() == other
}
}
impl<const N: usize> PartialEq<TinyString<N>> for &str {
fn eq(&self, other: &TinyString<N>) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl<const N: usize> PartialOrd<TinyString<N>> for TinyString<N> {
#[inline]
fn partial_cmp(&self, other: &TinyString<N>) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<const N: usize> Ord for TinyString<N> {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.buf.cmp(&other.buf)
}
}
impl<const N: usize> Clone for TinyString<N> {
#[inline]
fn clone(&self) -> Self {
Self { buf: self.buf.clone() }
}
#[inline]
fn clone_from(&mut self, source: &Self) {
self.buf.clone_from(&source.buf);
}
}
impl<const N: usize> AsRef<[u8]> for TinyString<N> {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const N: usize> AsRef<str> for TinyString<N> {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> AsMut<str> for TinyString<N> {
#[inline]
fn as_mut(&mut self) -> &mut str {
self.as_mut_str()
}
}
impl<const N: usize> AsRef<TinyString<N>> for TinyString<N> {
#[inline]
fn as_ref(&self) -> &TinyString<N> {
self
}
}
impl<const N: usize> AsMut<TinyString<N>> for TinyString<N> {
#[inline]
fn as_mut(&mut self) -> &mut TinyString<N> {
self
}
}
impl<const N: usize> Hash for TinyString<N> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.buf.hash(state);
}
}
impl<const N: usize> fmt::Debug for TinyString<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", self.bytes())
}
}
impl<const N: usize> Display for TinyString<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl<const N: usize> FromStr for TinyString<N> {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s))
}
}
impl<const N: usize> core::fmt::Write for TinyString<N> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
self.push(c);
Ok(())
}
}
impl<'a, const N: usize> AddAssign<&'a str> for TinyString<N> {
#[inline]
fn add_assign(&mut self, rhs: &'a str) {
self.push_str(rhs);
}
}
#[cfg(feature = "serde")]
impl<const N: usize> serde::Serialize for TinyString<N> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer
{
self.buf.serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, const N: usize> serde::Deserialize<'de> for TinyString<N> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>
{
let buf = TinyVec::<u8, N>::deserialize(deserializer)?;
Ok(Self { buf })
}
}
#[cfg(test)]
mod test;