use crate::{normal::NormalString, stack::StackString};
use std::mem::{size_of, ManuallyDrop};
const STACK_STRING_SIZE: usize = size_of::<NormalString>() - size_of::<*const u8>();
union FastStrInner {
ptr: *const u8,
inline: (*const u8, StackString<STACK_STRING_SIZE>),
normal: ManuallyDrop<NormalString>,
}
#[repr(transparent)]
pub struct FastStr(FastStrInner);
impl FastStrInner {
#[inline(always)]
fn is_inline(&self) -> bool {
unsafe { self.ptr.is_null() }
}
#[inline(always)]
fn get_inline_ref(&self) -> &StackString<STACK_STRING_SIZE> {
unsafe { &self.inline.1 }
}
#[inline(always)]
fn get_inline(&self) -> StackString<STACK_STRING_SIZE> {
unsafe { self.inline.1 }
}
#[inline(always)]
fn get_normal_ref(&self) -> &NormalString {
unsafe { &self.normal }
}
#[inline]
fn get_normal(mut self) -> NormalString {
unsafe {
let result = ManuallyDrop::take(&mut self.normal);
std::mem::forget(self);
result
}
}
#[inline(always)]
const fn from_inline(inline: StackString<STACK_STRING_SIZE>) -> Self {
Self {
inline: (std::ptr::null(), inline),
}
}
#[inline(always)]
const fn from_normal(normal: NormalString) -> Self {
Self {
normal: ManuallyDrop::new(normal),
}
}
}
#[allow(unused)]
impl FastStr {
pub(super) fn do_sub_with<
'a,
R: 'a,
F: FnOnce(&'a str, Box<dyn Fn(&str) -> Self + 'a>) -> R,
>(
&'a self,
f: F,
) -> R {
if self.is_inline() {
let inline = self.get_inline_ref();
f(
inline.as_str(),
Box::new(|str| Self::from_inline(inline.map_ref(str))),
)
} else {
let normal = self.get_normal_ref();
f(
normal.as_str(),
Box::new(|str| Self::from_normal(normal.map_ref(str))),
)
}
}
pub(super) fn do_sub_into<F: FnOnce(&str) -> &str>(self, f: F) -> Self {
if self.is_inline() {
let inline = self.get_inline();
let str = f(inline.as_str());
Self::from_inline(inline.map_ref_into(str))
} else {
let normal = self.get_normal();
let str = f(normal.str);
Self::from_normal(normal.map_ref_into(str))
}
}
#[inline(always)]
fn is_inline(&self) -> bool {
self.0.is_inline()
}
#[inline(always)]
fn get_inline_ref(&self) -> &StackString<STACK_STRING_SIZE> {
self.0.get_inline_ref()
}
#[inline(always)]
fn get_inline(&self) -> StackString<STACK_STRING_SIZE> {
self.0.get_inline()
}
#[inline(always)]
fn get_normal_ref(&self) -> &NormalString {
self.0.get_normal_ref()
}
#[inline(always)]
fn get_normal(self) -> NormalString {
self.0.get_normal()
}
#[inline(always)]
const fn from_inline(inline: StackString<STACK_STRING_SIZE>) -> Self {
Self(FastStrInner::from_inline(inline))
}
#[inline(always)]
const fn from_normal(normal: NormalString) -> Self {
Self(FastStrInner::from_normal(normal))
}
#[inline]
pub const fn new() -> Self {
Self::from_static("")
}
#[inline]
pub const fn from_static(str: &'static str) -> Self {
Self::from_normal(NormalString::from_static(str))
}
#[inline]
pub fn from_string(str: String) -> Self {
Self::from_normal(NormalString::from_string(str))
}
pub fn from_ref(str: &str) -> Self {
if str.len() <= StackString::<STACK_STRING_SIZE>::MAX_LEN {
Self::from_inline(StackString::new(str))
} else {
Self::from_normal(NormalString::from_string(str.into()))
}
}
pub fn into_string(self) -> String {
if self.is_inline() {
self.get_inline().as_str().into()
} else {
self.get_normal().into_string()
}
}
pub fn as_str(&self) -> &str {
if self.is_inline() {
self.get_inline_ref().as_str()
} else {
self.get_normal_ref().as_str()
}
}
pub fn is_static(&self) -> bool {
if self.is_inline() {
false
} else {
self.get_normal_ref().is_static()
}
}
#[inline]
pub fn static_str(&self) -> Option<&'static str> {
if self.is_inline() {
None
} else {
self.get_normal_ref().static_str()
}
}
}
impl Clone for FastStr {
fn clone(&self) -> Self {
if self.is_inline() {
Self::from_inline(self.get_inline())
} else {
Self::from_normal(self.get_normal_ref().clone())
}
}
}
impl Drop for FastStrInner {
fn drop(&mut self) {
if !self.is_inline() {
unsafe { ManuallyDrop::drop(&mut self.normal) }
}
}
}