#![warn(missing_docs)]
use std::{ffi::OsStr, fmt, mem::MaybeUninit, ops, ptr, slice, str, string};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FromUtf8Error {
bytes: Vec<u8>,
error: str::Utf8Error,
}
#[derive(Clone)]
pub struct FlexibleString<const CAPACITY: usize>(FlexibleStringInner<CAPACITY>);
#[derive(Clone)]
enum FlexibleStringInner<const CAPACITY: usize> {
Stack(StackString<CAPACITY>),
Heap(String),
}
macro_rules! common_methods_inner {
(NEVER_UPGRADE => fn_name:$fn_name:ident, generics:$(<$generics:tt>)?, self_qual:$([$($self_qual:tt)*])?, self:$self:ident, arg_name:[$($arg_name:ident),*]) => {
match $($($self_qual)*)? $self.0 {
FlexibleStringInner::Stack(s) => s.$fn_name $(::<$generics>)? ($($arg_name),*),
FlexibleStringInner::Heap(h) => h.$fn_name $(::<$generics>)? ($($arg_name),*),
}
};
(MAYBE_UPGRADE => fn_name:$fn_name:ident, generics:$(<$generics:tt>)?, self_qual:$([$($self_qual:tt)*])?, self:$self:ident, arg_name:[$($arg_name:ident),*]) => {
match $($($self_qual)*)? $self.0 {
FlexibleStringInner::Stack(s) => {
match s.$fn_name $(::<$generics>)? ($($arg_name),*) {
Err(capacity) => {
let mut heap = s.to_heap(capacity);
let res = heap.$fn_name($($arg_name),*);
*$self = Self(FlexibleStringInner::Heap(heap));
res
},
Ok(res) => res
}
},
FlexibleStringInner::Heap(h) => h.$fn_name $(::<$generics>)? ($($arg_name),*),
}
};
}
macro_rules! common_methods {
() => {};
( $(#[$attr:meta])*
$upgrade:ident => $vis:vis $([$($qual:tt)*])? fn $fn_name:ident $(<$generics:tt>)? ( $([$($self_qual:tt)*])? $self:ident $(,)? $( $arg_name:ident: $arg_type:ty),* ) $(-> $ret:ty)?
$(where $($where_ty:ty: $where_bound:path)*)?; $($tail:tt)* ) => {
$(#[$attr])*
#[inline]
$vis $($($qual)*)? fn $fn_name $(<$generics>)? ($($($self_qual)*)? $self, $($arg_name: $arg_type),*) $(-> $ret)?
$(where $($where_ty: $where_bound)*)? {
common_methods_inner!($upgrade => fn_name:$fn_name, generics:$(<$generics>)?, self_qual:$([$($self_qual)*])?, self:$self, arg_name:[$($arg_name),*])
}
common_methods!($($tail)*);
};
}
impl<const CAPACITY: usize> FlexibleString<CAPACITY> {
#[inline]
#[must_use]
pub fn new() -> Self {
Self(FlexibleStringInner::Stack(StackString::new()))
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
if capacity > CAPACITY {
Self(FlexibleStringInner::Heap(String::with_capacity(capacity)))
} else {
Self(FlexibleStringInner::Stack(StackString::new()))
}
}
#[inline]
pub fn from_utf8(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
if vec.len() > CAPACITY {
String::from_utf8(vec)
.map(|s| Self(FlexibleStringInner::Heap(s)))
.map_err(|err| err.into())
} else {
unsafe { StackString::from_utf8_vec_only_len_unchecked(vec) }
.map(|s| Self(FlexibleStringInner::Stack(s)))
}
}
#[inline]
#[must_use]
pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> Self {
if bytes.len() > CAPACITY {
Self(FlexibleStringInner::Heap(String::from_utf8_unchecked(
bytes,
)))
} else {
Self(FlexibleStringInner::Stack(
StackString::from_utf8_slice_unchecked(&bytes),
))
}
}
common_methods! {
#[must_use = "`self` will be dropped if the result is not used"]
NEVER_UPGRADE => pub fn into_bytes(self) -> Vec<u8>;
#[must_use]
NEVER_UPGRADE => pub fn as_str([&]self) -> &str;
#[must_use]
NEVER_UPGRADE => pub fn as_mut_str([&mut]self) -> &mut str;
MAYBE_UPGRADE => pub fn push_str([&mut]self, string: &str);
NEVER_UPGRADE => pub fn capacity([&]self) -> usize;
MAYBE_UPGRADE => pub fn reserve([&mut]self, additional: usize);
MAYBE_UPGRADE => pub fn reserve_exact([&mut]self, additional: usize);
NEVER_UPGRADE => pub fn shrink_to([&mut]self, min_capacity: usize);
MAYBE_UPGRADE => pub fn push([&mut]self, ch: char);
#[must_use]
NEVER_UPGRADE => pub fn as_bytes([&]self) -> &[u8];
NEVER_UPGRADE => pub fn truncate([&mut]self, new_len: usize);
NEVER_UPGRADE => pub fn pop([&mut]self) -> Option<char>;
NEVER_UPGRADE => pub fn remove([&mut]self, idx: usize) -> char;
MAYBE_UPGRADE => pub fn insert([&mut]self, idx: usize, ch: char);
MAYBE_UPGRADE => pub fn insert_str([&mut]self, idx: usize, string: &str);
NEVER_UPGRADE => pub fn len([&]self) -> usize;
NEVER_UPGRADE => pub fn is_empty([&]self) -> bool;
NEVER_UPGRADE => pub fn clear([&mut]self);
}
}
impl<const CAPACITY: usize> Default for FlexibleString<CAPACITY> {
#[inline]
fn default() -> Self {
Self::new()
}
}
macro_rules! impl_eq {
($lhs:ty, $rhs:ty) => {
#[allow(unused_lifetimes)]
impl<'a, 'b, const CAPACITY: usize> PartialEq<$rhs> for $lhs {
#[inline]
fn eq(&self, other: &$rhs) -> bool {
PartialEq::eq(&self[..], &other[..])
}
}
#[allow(unused_lifetimes)]
impl<'a, 'b, const CAPACITY: usize> PartialEq<$lhs> for $rhs {
#[inline]
fn eq(&self, other: &$lhs) -> bool {
PartialEq::eq(&self[..], &other[..])
}
}
};
}
impl_eq! { FlexibleString<CAPACITY>, str }
impl_eq! { FlexibleString<CAPACITY>, &'a str }
impl_eq! { FlexibleString<CAPACITY>, String }
impl<const CAPACITY_LHS: usize, const CAPACITY_RHS: usize> PartialEq<FlexibleString<CAPACITY_RHS>>
for FlexibleString<CAPACITY_LHS>
{
#[inline]
fn eq(&self, other: &FlexibleString<CAPACITY_RHS>) -> bool {
PartialEq::eq(&self[..], &other[..])
}
}
impl<const CAPACITY: usize> AsRef<str> for FlexibleString<CAPACITY> {
#[inline]
fn as_ref(&self) -> &str {
self
}
}
impl<const CAPACITY: usize> AsMut<str> for FlexibleString<CAPACITY> {
#[inline]
fn as_mut(&mut self) -> &mut str {
self
}
}
impl<const CAPACITY: usize> AsRef<[u8]> for FlexibleString<CAPACITY> {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const CAPACITY: usize> AsRef<OsStr> for FlexibleString<CAPACITY> {
#[inline]
fn as_ref(&self) -> &OsStr {
(&**self).as_ref()
}
}
impl<const CAPACITY: usize> From<char> for FlexibleString<CAPACITY> {
#[inline]
fn from(c: char) -> Self {
FlexibleString::from(c.encode_utf8(&mut [0; 4]))
}
}
impl<const CAPACITY: usize> From<&str> for FlexibleString<CAPACITY> {
#[inline]
fn from(s: &str) -> Self {
let bytes = s.as_bytes();
if bytes.len() > CAPACITY {
let owned_bytes = bytes.to_owned();
Self(FlexibleStringInner::Heap(unsafe {
String::from_utf8_unchecked(owned_bytes)
}))
} else {
Self(FlexibleStringInner::Stack(unsafe {
StackString::from_utf8_slice_unchecked(bytes)
}))
}
}
}
impl<const CAPACITY: usize> From<&mut str> for FlexibleString<CAPACITY> {
#[inline]
fn from(s: &mut str) -> Self {
(s as &str).into()
}
}
impl<const CAPACITY: usize> From<String> for FlexibleString<CAPACITY> {
#[inline]
fn from(s: String) -> Self {
s.as_str().into()
}
}
impl<'a, const CAPACITY: usize> FromIterator<&'a char> for FlexibleString<CAPACITY> {
fn from_iter<I: IntoIterator<Item = &'a char>>(iter: I) -> Self {
let mut buf = FlexibleString::new();
buf.extend(iter);
buf
}
}
impl<'a, const CAPACITY: usize> FromIterator<&'a str> for FlexibleString<CAPACITY> {
fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
let mut buf = FlexibleString::new();
buf.extend(iter);
buf
}
}
impl<const CAPACITY_LHS: usize, const CAPACITY_RHS: usize>
FromIterator<FlexibleString<CAPACITY_RHS>> for FlexibleString<CAPACITY_LHS>
{
fn from_iter<I: IntoIterator<Item = FlexibleString<CAPACITY_RHS>>>(
iter: I,
) -> FlexibleString<CAPACITY_LHS> {
let mut buf = FlexibleString::new();
buf.extend(iter);
buf
}
}
impl<const CAPACITY: usize> FromIterator<char> for FlexibleString<CAPACITY> {
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
let mut buf = FlexibleString::new();
buf.extend(iter);
buf
}
}
impl<const CAPACITY: usize> FromIterator<Box<str>> for FlexibleString<CAPACITY> {
fn from_iter<I: IntoIterator<Item = Box<str>>>(iter: I) -> Self {
let mut buf = FlexibleString::new();
buf.extend(iter);
buf
}
}
impl<const CAPACITY: usize> Extend<char> for FlexibleString<CAPACITY> {
fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
let iterator = iter.into_iter();
let (lower_bound, _) = iterator.size_hint();
self.reserve(lower_bound);
iterator.for_each(move |c| self.push(c));
}
}
impl<'a, const CAPACITY: usize> Extend<&'a char> for FlexibleString<CAPACITY> {
fn extend<I: IntoIterator<Item = &'a char>>(&mut self, iter: I) {
self.extend(iter.into_iter().cloned());
}
}
impl<'a, const CAPACITY: usize> Extend<&'a str> for FlexibleString<CAPACITY> {
fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
iter.into_iter().for_each(move |s| self.push_str(s));
}
}
impl<'a, const CAPACITY: usize> Extend<Box<str>> for FlexibleString<CAPACITY> {
fn extend<I: IntoIterator<Item = Box<str>>>(&mut self, iter: I) {
iter.into_iter().for_each(move |s| self.push_str(&s));
}
}
impl<const CAPACITY_LHS: usize, const CAPACITY_RHS: usize> Extend<FlexibleString<CAPACITY_RHS>>
for FlexibleString<CAPACITY_LHS>
{
fn extend<I: IntoIterator<Item = FlexibleString<CAPACITY_RHS>>>(&mut self, iter: I) {
iter.into_iter().for_each(move |s| self.push_str(&s));
}
}
impl<const CAPACITY: usize> str::FromStr for FlexibleString<CAPACITY> {
type Err = core::convert::Infallible;
#[inline]
fn from_str(s: &str) -> Result<FlexibleString<CAPACITY>, Self::Err> {
Ok(FlexibleString::from(s))
}
}
impl<const CAPACITY: usize> ops::Add<&str> for FlexibleString<CAPACITY> {
type Output = FlexibleString<CAPACITY>;
#[inline]
fn add(mut self, other: &str) -> Self {
self.push_str(other);
self
}
}
impl<const CAPACITY: usize> ops::AddAssign<&str> for FlexibleString<CAPACITY> {
#[inline]
fn add_assign(&mut self, other: &str) {
self.push_str(other);
}
}
impl<const CAPACITY: usize> ops::Deref for FlexibleString<CAPACITY> {
type Target = str;
common_methods! {
NEVER_UPGRADE => fn deref([&]self) -> &str;
}
}
impl<const CAPACITY: usize> ops::DerefMut for FlexibleString<CAPACITY> {
common_methods! {
NEVER_UPGRADE => fn deref_mut([&mut]self) -> &mut str;
}
}
impl<const CAPACITY: usize> fmt::Write for FlexibleString<CAPACITY> {
#[inline]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
#[inline]
fn write_char(&mut self, c: char) -> fmt::Result {
self.push(c);
Ok(())
}
}
impl<const CAPACITY: usize> fmt::Display for FlexibleString<CAPACITY> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
impl<const CAPACITY: usize> fmt::Debug for FlexibleString<CAPACITY> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[derive(Clone)]
struct StackString<const CAPACITY: usize> {
data: [MaybeUninit<u8>; CAPACITY],
len: usize,
}
type StackStringResult<T> = Result<T, Option<usize>>;
impl<const CAPACITY: usize> StackString<CAPACITY> {
#[inline]
fn new() -> Self {
Self {
data: unsafe { MaybeUninit::uninit().assume_init() },
len: 0,
}
}
#[inline]
unsafe fn from_utf8_vec_only_len_unchecked(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
match str::from_utf8(&vec) {
Ok(..) => Ok(Self::from_utf8_slice_unchecked(&vec)),
Err(e) => Err(FromUtf8Error {
bytes: vec,
error: e,
}),
}
}
#[inline]
unsafe fn from_utf8_slice_unchecked(bytes: &[u8]) -> Self {
let mut res = Self::new();
res.copy_append_unchecked(bytes.as_ptr(), bytes.len());
res
}
#[inline]
fn into_bytes(self) -> Vec<u8> {
self.as_bytes().into()
}
#[inline]
fn as_str(&self) -> &str {
self
}
#[inline]
fn as_mut_str(&mut self) -> &mut str {
self
}
#[inline]
fn push_str(&mut self, string: &str) -> StackStringResult<()> {
let len_needed = self.len + string.len();
if len_needed > CAPACITY {
Err(Some(len_needed))
} else {
unsafe {
self.copy_append_unchecked(string.as_ptr(), string.len());
}
Ok(())
}
}
#[inline]
const fn capacity(&self) -> usize {
CAPACITY
}
#[inline]
fn reserve(&mut self, additional: usize) -> StackStringResult<()> {
let cap_at_least = self.len + additional;
if cap_at_least > CAPACITY {
Err(Some(cap_at_least))
} else {
Ok(())
}
}
#[inline]
fn reserve_exact(&mut self, additional: usize) -> StackStringResult<()> {
self.reserve(additional)
}
#[inline]
fn shrink_to(&mut self, _min_capacity: usize) {
}
#[inline]
fn push(&mut self, ch: char) -> StackStringResult<()> {
let len_utf8 = ch.len_utf8();
let len_needed = self.len + len_utf8;
if len_needed > CAPACITY {
Err(Some(len_needed))
} else {
match len_utf8 {
1 => {
unsafe { *self.data[self.len].as_mut_ptr() = ch as u8 }
self.len += 1;
}
test_len_utf8 => {
let mut buf = [0; 4];
let bytes = ch.encode_utf8(&mut buf).as_bytes();
debug_assert_eq!(test_len_utf8, bytes.len());
unsafe { self.copy_append_unchecked(bytes.as_ptr(), bytes.len()) }
}
}
Ok(())
}
}
#[inline]
fn as_bytes(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.as_ptr(), self.len) }
}
#[inline]
fn as_bytes_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) }
}
#[inline]
fn truncate(&mut self, new_len: usize) {
if new_len <= self.len {
assert!(self.is_char_boundary(new_len));
unsafe {
self.set_len(new_len);
}
}
}
#[inline]
fn pop(&mut self) -> Option<char> {
let ch = self.chars().rev().next()?;
let newlen = self.len() - ch.len_utf8();
unsafe {
self.set_len(newlen);
}
Some(ch)
}
#[inline]
fn remove(&mut self, idx: usize) -> char {
let ch = match self[idx..].chars().next() {
Some(ch) => ch,
None => panic!("cannot remove a char from the end of a string"),
};
let next = idx + ch.len_utf8();
let len = self.len();
unsafe {
ptr::copy(
self.as_ptr().add(next),
self.as_mut_ptr().add(idx),
len - next,
);
self.set_len(len - (next - idx));
}
ch
}
#[inline]
fn insert(&mut self, idx: usize, ch: char) -> StackStringResult<()> {
assert!(self.is_char_boundary(idx));
let mut bits = [0; 4];
let bits = ch.encode_utf8(&mut bits).as_bytes();
unsafe { self.insert_bytes(idx, bits) }
}
#[inline]
fn insert_str(&mut self, idx: usize, string: &str) -> StackStringResult<()> {
assert!(self.is_char_boundary(idx));
unsafe { self.insert_bytes(idx, string.as_bytes()) }
}
#[inline]
fn len(&self) -> usize {
self.len
}
#[inline]
fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
fn clear(&mut self) {
self.truncate(0)
}
#[inline]
unsafe fn set_len(&mut self, new_len: usize) {
debug_assert!(new_len <= CAPACITY);
self.len = new_len;
}
#[inline]
fn as_ptr(&self) -> *const u8 {
self.data.as_ptr() as *const u8
}
#[inline]
fn as_mut_ptr(&mut self) -> *mut u8 {
self.data.as_mut_ptr() as *mut u8
}
#[inline]
unsafe fn copy_append_unchecked(&mut self, src: *const u8, len: usize) {
let dst = self.as_mut_ptr().add(self.len);
ptr::copy_nonoverlapping(src, dst, len);
self.len += len;
}
#[inline]
fn to_heap(&self, estimated_capacity: Option<usize>) -> String {
let bytes = self.as_bytes();
if let Some(capacity) = estimated_capacity {
let mut vec = Vec::with_capacity(capacity);
vec.extend_from_slice(bytes);
unsafe { String::from_utf8_unchecked(vec) }
} else {
unsafe { String::from_utf8_unchecked(bytes.into()) }
}
}
#[inline]
unsafe fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) -> StackStringResult<()> {
let len = self.len();
let amt = bytes.len();
let len_needed = len + amt;
if len_needed > CAPACITY {
return Err(Some(len_needed));
}
ptr::copy(
self.as_ptr().add(idx),
self.as_mut_ptr().add(idx + amt),
len - idx,
);
ptr::copy_nonoverlapping(bytes.as_ptr(), self.as_mut_ptr().add(idx), amt);
self.set_len(len_needed);
Ok(())
}
}
impl<const CAPACITY: usize> ops::Deref for StackString<CAPACITY> {
type Target = str;
#[inline]
fn deref(&self) -> &str {
let bytes = self.as_bytes();
unsafe { str::from_utf8_unchecked(bytes) }
}
}
impl<const CAPACITY: usize> ops::DerefMut for StackString<CAPACITY> {
#[inline]
fn deref_mut(&mut self) -> &mut str {
let bytes = self.as_bytes_mut();
unsafe { str::from_utf8_unchecked_mut(bytes) }
}
}
impl<const CAPACITY: usize> fmt::Debug for StackString<CAPACITY> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl FromUtf8Error {
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..]
}
#[inline]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
#[inline]
pub fn utf8_error(&self) -> str::Utf8Error {
self.error
}
}
impl From<string::FromUtf8Error> for FromUtf8Error {
fn from(std_err: string::FromUtf8Error) -> Self {
let err = std_err.utf8_error();
Self {
bytes: std_err.into_bytes(),
error: err,
}
}
}
impl fmt::Display for FromUtf8Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.error, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn heterogeneous_cmp() {
assert_eq!(
FlexibleString::<100>::from("hello"),
FlexibleString::<200>::from("hello")
);
assert_eq!(FlexibleString::<100>::from("hello"), "hello");
assert_eq!("hello", FlexibleString::<100>::from("hello"));
assert_eq!(FlexibleString::<100>::from("hello"), String::from("hello"));
assert_eq!(String::from("hello"), FlexibleString::<100>::from("hello"));
}
}