1 use std
::{borrow::Cow, fmt}
;
3 use crate::stack
::StackString
;
4 use crate::KStringCowBase
;
7 pub(crate) type StdString
= std
::string
::String
;
9 /// A UTF-8 encoded, immutable string.
10 pub type KString
= KStringBase
<crate::backend
::DefaultStr
>;
12 /// A UTF-8 encoded, immutable string.
15 pub struct KStringBase
<B
> {
16 inner
: KStringInner
<B
>,
19 impl<B
> KStringBase
<B
> {
20 pub const EMPTY
: Self = KStringBase
::from_static("");
22 /// Create a new empty `KStringBase`.
25 pub fn new() -> Self {
29 /// Create a reference to a `'static` data.
32 pub const fn from_static(other
: &'
static str) -> Self {
34 inner
: KStringInner
::from_static(other
),
38 /// Create an inline string, if possible
41 pub fn try_inline(other
: &str) -> Option
<Self> {
42 KStringInner
::try_inline(other
).map(|inner
| Self { inner }
)
46 impl<B
: crate::backend
::HeapStr
> KStringBase
<B
> {
47 /// Create an owned `KStringBase`.
50 pub fn from_boxed(other
: crate::backend
::BoxedStr
) -> Self {
52 inner
: KStringInner
::from_boxed(other
),
56 /// Create an owned `KStringBase`.
59 pub fn from_string(other
: StdString
) -> Self {
61 inner
: KStringInner
::from_string(other
),
65 /// Create an owned `KStringBase` optimally from a reference.
68 pub fn from_ref(other
: &str) -> Self {
70 inner
: KStringInner
::from_ref(other
),
74 /// Get a reference to the `KStringBase`.
77 pub fn as_ref(&self) -> KStringRef
<'_
> {
81 /// Extracts a string slice containing the entire `KStringBase`.
84 pub fn as_str(&self) -> &str {
88 /// Convert to a mutable string type, cloning the data if necessary.
91 pub fn into_string(self) -> StdString
{
92 String
::from(self.into_boxed_str())
95 /// Convert to a mutable string type, cloning the data if necessary.
98 pub fn into_boxed_str(self) -> crate::backend
::BoxedStr
{
99 self.inner
.into_boxed_str()
102 /// Convert to a Cow str
105 pub fn into_cow_str(self) -> Cow
<'
static, str> {
106 self.inner
.into_cow_str()
110 impl<B
: crate::backend
::HeapStr
> std
::ops
::Deref
for KStringBase
<B
> {
114 fn deref(&self) -> &str {
119 impl<B
: crate::backend
::HeapStr
> Eq
for KStringBase
<B
> {}
121 impl<'s
, B
: crate::backend
::HeapStr
> PartialEq
<KStringBase
<B
>> for KStringBase
<B
> {
123 fn eq(&self, other
: &Self) -> bool
{
124 PartialEq
::eq(self.as_str(), other
.as_str())
128 impl<'s
, B
: crate::backend
::HeapStr
> PartialEq
<str> for KStringBase
<B
> {
130 fn eq(&self, other
: &str) -> bool
{
131 PartialEq
::eq(self.as_str(), other
)
135 impl<'s
, B
: crate::backend
::HeapStr
> PartialEq
<&'s
str> for KStringBase
<B
> {
137 fn eq(&self, other
: &&str) -> bool
{
138 PartialEq
::eq(self.as_str(), *other
)
142 impl<'s
, B
: crate::backend
::HeapStr
> PartialEq
<String
> for KStringBase
<B
> {
144 fn eq(&self, other
: &StdString
) -> bool
{
145 PartialEq
::eq(self.as_str(), other
.as_str())
149 impl<B
: crate::backend
::HeapStr
> Ord
for KStringBase
<B
> {
151 fn cmp(&self, other
: &Self) -> std
::cmp
::Ordering
{
152 self.as_str().cmp(other
.as_str())
156 impl<B
: crate::backend
::HeapStr
> PartialOrd
for KStringBase
<B
> {
158 fn partial_cmp(&self, other
: &Self) -> Option
<std
::cmp
::Ordering
> {
159 self.as_str().partial_cmp(other
.as_str())
163 impl<B
: crate::backend
::HeapStr
> std
::hash
::Hash
for KStringBase
<B
> {
165 fn hash
<H
: std
::hash
::Hasher
>(&self, state
: &mut H
) {
166 self.as_str().hash(state
);
170 impl<B
: crate::backend
::HeapStr
> fmt
::Debug
for KStringBase
<B
> {
172 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
177 impl<B
: crate::backend
::HeapStr
> fmt
::Display
for KStringBase
<B
> {
179 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
180 fmt
::Display
::fmt(self.as_str(), f
)
184 impl<B
: crate::backend
::HeapStr
> AsRef
<str> for KStringBase
<B
> {
186 fn as_ref(&self) -> &str {
191 impl<B
: crate::backend
::HeapStr
> AsRef
<[u8]> for KStringBase
<B
> {
193 fn as_ref(&self) -> &[u8] {
198 impl<B
: crate::backend
::HeapStr
> AsRef
<std
::ffi
::OsStr
> for KStringBase
<B
> {
200 fn as_ref(&self) -> &std
::ffi
::OsStr
{
205 impl<B
: crate::backend
::HeapStr
> AsRef
<std
::path
::Path
> for KStringBase
<B
> {
207 fn as_ref(&self) -> &std
::path
::Path
{
208 std
::path
::Path
::new(self)
212 impl<B
: crate::backend
::HeapStr
> std
::borrow
::Borrow
<str> for KStringBase
<B
> {
214 fn borrow(&self) -> &str {
219 impl<B
: crate::backend
::HeapStr
> Default
for KStringBase
<B
> {
221 fn default() -> Self {
226 impl<'s
, B
: crate::backend
::HeapStr
> From
<KStringRef
<'s
>> for KStringBase
<B
> {
228 fn from(other
: KStringRef
<'s
>) -> Self {
233 impl<'s
, B
: crate::backend
::HeapStr
> From
<&'s KStringRef
<'s
>> for KStringBase
<B
> {
235 fn from(other
: &'s KStringRef
<'s
>) -> Self {
240 impl<'s
, B
: crate::backend
::HeapStr
> From
<KStringCowBase
<'s
, B
>> for KStringBase
<B
> {
242 fn from(other
: KStringCowBase
<'s
, B
>) -> Self {
247 impl<'s
, B
: crate::backend
::HeapStr
> From
<&'s KStringCowBase
<'s
, B
>> for KStringBase
<B
> {
249 fn from(other
: &'s KStringCowBase
<'s
, B
>) -> Self {
250 other
.clone().into_owned()
254 impl<B
: crate::backend
::HeapStr
> From
<StdString
> for KStringBase
<B
> {
256 fn from(other
: StdString
) -> Self {
257 Self::from_string(other
)
261 impl<'s
, B
: crate::backend
::HeapStr
> From
<&'s StdString
> for KStringBase
<B
> {
263 fn from(other
: &'s StdString
) -> Self {
264 Self::from_ref(other
)
268 impl<B
: crate::backend
::HeapStr
> From
<crate::backend
::BoxedStr
> for KStringBase
<B
> {
270 fn from(other
: crate::backend
::BoxedStr
) -> Self {
271 Self::from_boxed(other
)
275 impl<'s
, B
: crate::backend
::HeapStr
> From
<&'s
crate::backend
::BoxedStr
> for KStringBase
<B
> {
277 fn from(other
: &'s
crate::backend
::BoxedStr
) -> Self {
278 Self::from_ref(other
)
282 impl<B
: crate::backend
::HeapStr
> From
<&'
static str> for KStringBase
<B
> {
284 fn from(other
: &'
static str) -> Self {
285 Self::from_static(other
)
289 impl<B
: crate::backend
::HeapStr
> std
::str::FromStr
for KStringBase
<B
> {
290 type Err
= std
::convert
::Infallible
;
292 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
293 Ok(Self::from_ref(s
))
297 #[cfg(feature = "serde")]
298 impl<B
: crate::backend
::HeapStr
> serde
::Serialize
for KStringBase
<B
> {
300 fn serialize
<S
>(&self, serializer
: S
) -> Result
<S
::Ok
, S
::Error
>
302 S
: serde
::Serializer
,
304 serializer
.serialize_str(self.as_str())
308 #[cfg(feature = "serde")]
309 impl<'de
, B
: crate::backend
::HeapStr
> serde
::Deserialize
<'de
> for KStringBase
<B
> {
310 fn deserialize
<D
>(deserializer
: D
) -> Result
<Self, D
::Error
>
312 D
: serde
::Deserializer
<'de
>,
314 deserializer
.deserialize_string(StringVisitor(std
::marker
::PhantomData
))
318 #[cfg(feature = "serde")]
319 struct StringVisitor
<B
>(std
::marker
::PhantomData
<B
>);
321 #[cfg(feature = "serde")]
322 impl<'de
, B
: crate::backend
::HeapStr
> serde
::de
::Visitor
<'de
> for StringVisitor
<B
> {
323 type Value
= KStringBase
<B
>;
325 fn expecting(&self, formatter
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
326 formatter
.write_str("a string")
329 fn visit_str
<E
>(self, v
: &str) -> Result
<Self::Value
, E
>
333 Ok(Self::Value
::from_ref(v
))
336 fn visit_string
<E
>(self, v
: String
) -> Result
<Self::Value
, E
>
340 Ok(Self::Value
::from_string(v
))
343 fn visit_bytes
<E
>(self, v
: &[u8]) -> Result
<Self::Value
, E
>
347 match std
::str::from_utf8(v
) {
348 Ok(s
) => Ok(Self::Value
::from_ref(s
)),
349 Err(_
) => Err(serde
::de
::Error
::invalid_value(
350 serde
::de
::Unexpected
::Bytes(v
),
356 fn visit_byte_buf
<E
>(self, v
: Vec
<u8>) -> Result
<Self::Value
, E
>
360 match String
::from_utf8(v
) {
361 Ok(s
) => Ok(Self::Value
::from_string(s
)),
362 Err(e
) => Err(serde
::de
::Error
::invalid_value(
363 serde
::de
::Unexpected
::Bytes(&e
.into_bytes()),
370 use inner
::KStringInner
;
372 #[cfg(not(feature = "unsafe"))]
376 pub(super) enum KStringInner
<B
> {
377 Singleton(&'
static str),
378 Inline(StackString
<CAPACITY
>),
382 impl<B
> KStringInner
<B
> {
383 /// Create a reference to a `'static` data.
385 pub const fn from_static(other
: &'
static str) -> Self {
386 Self::Singleton(other
)
390 pub fn try_inline(other
: &str) -> Option
<Self> {
391 StackString
::try_new(other
).map(Self::Inline
)
395 impl<B
: crate::backend
::HeapStr
> KStringInner
<B
> {
397 pub(super) fn from_boxed(other
: crate::backend
::BoxedStr
) -> Self {
398 #[allow(clippy::useless_conversion)]
399 Self::Owned(B
::from_boxed_str(other
))
403 pub(super) fn from_string(other
: StdString
) -> Self {
404 if (0..=CAPACITY
).contains(&other
.len()) {
405 let inline
= { StackString::new(other.as_str()) }
;
408 Self::from_boxed(other
.into_boxed_str())
413 pub(super) fn from_ref(other
: &str) -> Self {
414 if (0..=CAPACITY
).contains(&other
.len()) {
415 let inline
= { StackString::new(other) }
;
418 Self::Owned(B
::from_str(other
))
423 pub(super) fn as_ref(&self) -> KStringRef
<'_
> {
425 Self::Singleton(s
) => KStringRef
::from_static(s
),
426 Self::Inline(s
) => KStringRef
::from_ref(s
.as_str()),
427 Self::Owned(s
) => KStringRef
::from_ref(s
.as_str()),
432 pub(super) fn as_str(&self) -> &str {
434 Self::Singleton(s
) => s
,
435 Self::Inline(s
) => s
.as_str(),
436 Self::Owned(s
) => s
.as_str(),
441 pub(super) fn into_boxed_str(self) -> crate::backend
::BoxedStr
{
443 Self::Singleton(s
) => crate::backend
::BoxedStr
::from(s
),
444 Self::Inline(s
) => crate::backend
::BoxedStr
::from(s
.as_str()),
445 Self::Owned(s
) => crate::backend
::BoxedStr
::from(s
.as_str()),
449 /// Convert to a Cow str
451 pub(super) fn into_cow_str(self) -> Cow
<'
static, str> {
453 Self::Singleton(s
) => Cow
::Borrowed(s
),
454 Self::Inline(s
) => Cow
::Owned(s
.as_str().into()),
455 Self::Owned(s
) => Cow
::Owned(s
.as_str().into()),
460 // Explicit to avoid inlining which cuts clone times in half.
462 // An automatically derived `clone()` has 10ns overhead while the explicit `Deref`/`as_str` has
463 // none of that. Being explicit and removing the `#[inline]` attribute dropped the overhead to
466 // My only guess is that the `clone()` calls we delegate to are just that much bigger than
467 // `as_str()` that, when combined with a jump table, is blowing the icache, slowing things down.
468 impl<B
: Clone
> Clone
for KStringInner
<B
> {
469 fn clone(&self) -> Self {
471 Self::Singleton(s
) => Self::Singleton(s
),
472 Self::Inline(s
) => Self::Inline(*s
),
473 Self::Owned(s
) => Self::Owned(s
.clone()),
479 const LEN_SIZE
: usize = std
::mem
::size_of
::<crate::stack
::Len
>();
482 const TAG_SIZE
: usize = std
::mem
::size_of
::<u8>();
485 const MAX_CAPACITY
: usize =
486 std
::mem
::size_of
::<crate::string
::StdString
>() - TAG_SIZE
- LEN_SIZE
;
488 // Performance seems to slow down when trying to occupy all of the padding left by `String`'s
489 // discriminant. The question is whether faster len=1-16 "allocations" outweighs going to the heap
492 const ALIGNED_CAPACITY
: usize = std
::mem
::size_of
::<crate::backend
::DefaultStr
>() - LEN_SIZE
;
494 #[cfg(feature = "max_inline")]
495 const CAPACITY
: usize = MAX_CAPACITY
;
496 #[cfg(not(feature = "max_inline"))]
497 const CAPACITY
: usize = ALIGNED_CAPACITY
;
500 #[cfg(feature = "unsafe")]
504 pub(super) union KStringInner
<B
> {
506 singleton
: SingletonVariant
,
507 owned
: std
::mem
::ManuallyDrop
<OwnedVariant
<B
>>,
508 inline
: InlineVariant
,
511 impl<B
> KStringInner
<B
> {
512 /// Create a reference to a `'static` data.
514 pub const fn from_static(other
: &'
static str) -> Self {
516 singleton
: SingletonVariant
::new(other
),
521 pub fn try_inline(other
: &str) -> Option
<Self> {
522 StackString
::try_new(other
).map(|inline
| Self {
523 inline
: InlineVariant
::new(inline
),
528 const fn tag(&self) -> Tag
{
530 // SAFETY: `tag` is in the same spot in each variant
536 impl<B
: crate::backend
::HeapStr
> KStringInner
<B
> {
538 pub(super) fn from_boxed(other
: crate::backend
::BoxedStr
) -> Self {
539 #[allow(clippy::useless_conversion)]
540 let payload
= B
::from_boxed_str(other
);
542 owned
: std
::mem
::ManuallyDrop
::new(OwnedVariant
::new(payload
)),
547 pub(super) fn from_string(other
: StdString
) -> Self {
548 if (0..=CAPACITY
).contains(&other
.len()) {
549 let payload
= unsafe {
550 // SAFETY: range check ensured this is always safe
551 StackString
::new_unchecked(other
.as_str())
554 inline
: InlineVariant
::new(payload
),
557 Self::from_boxed(other
.into_boxed_str())
562 pub(super) fn from_ref(other
: &str) -> Self {
563 if (0..=CAPACITY
).contains(&other
.len()) {
564 let payload
= unsafe {
565 // SAFETY: range check ensured this is always safe
566 StackString
::new_unchecked(other
)
569 inline
: InlineVariant
::new(payload
),
572 #[allow(clippy::useless_conversion)]
573 let payload
= B
::from_str(other
);
575 owned
: std
::mem
::ManuallyDrop
::new(OwnedVariant
::new(payload
)),
581 pub(super) fn as_ref(&self) -> KStringRef
<'_
> {
582 let tag
= self.tag();
584 // SAFETY: `tag` ensures access to correct variant
585 if tag
.is_singleton() {
586 KStringRef
::from_static(self.singleton
.payload
)
587 } else if tag
.is_owned() {
588 KStringRef
::from_ref(self.owned
.payload
.as_str())
590 debug_assert
!(tag
.is_inline());
591 KStringRef
::from_ref(self.inline
.payload
.as_str())
597 pub(super) fn as_str(&self) -> &str {
598 let tag
= self.tag();
600 // SAFETY: `tag` ensures access to correct variant
601 if tag
.is_singleton() {
602 self.singleton
.payload
603 } else if tag
.is_owned() {
604 self.owned
.payload
.as_str()
606 debug_assert
!(tag
.is_inline());
607 self.inline
.payload
.as_str()
613 pub(super) fn into_boxed_str(self) -> crate::backend
::BoxedStr
{
614 let tag
= self.tag();
616 // SAFETY: `tag` ensures access to correct variant
617 if tag
.is_singleton() {
618 crate::backend
::BoxedStr
::from(self.singleton
.payload
)
619 } else if tag
.is_owned() {
620 crate::backend
::BoxedStr
::from(self.owned
.payload
.as_str())
622 debug_assert
!(tag
.is_inline());
623 crate::backend
::BoxedStr
::from(self.inline
.payload
.as_ref())
628 /// Convert to a Cow str
630 pub(super) fn into_cow_str(self) -> Cow
<'
static, str> {
631 let tag
= self.tag();
633 // SAFETY: `tag` ensures access to correct variant
634 if tag
.is_singleton() {
635 Cow
::Borrowed(self.singleton
.payload
)
636 } else if tag
.is_owned() {
637 Cow
::Owned(self.owned
.payload
.as_str().into())
639 debug_assert
!(tag
.is_inline());
640 Cow
::Owned(self.inline
.payload
.as_str().into())
646 // Explicit to avoid inlining which cuts clone times in half.
648 // An automatically derived `clone()` has 10ns overhead while the explicit `Deref`/`as_str` has
649 // none of that. Being explicit and removing the `#[inline]` attribute dropped the overhead to
652 // My only guess is that the `clone()` calls we delegate to are just that much bigger than
653 // `as_str()` that, when combined with a jump table, is blowing the icache, slowing things down.
654 impl<B
: Clone
> Clone
for KStringInner
<B
> {
655 fn clone(&self) -> Self {
656 let tag
= self.tag();
659 // SAFETY: `tag` ensures access to correct variant
661 owned
: std
::mem
::ManuallyDrop
::new(OwnedVariant
::new(
662 self.owned
.payload
.clone(),
668 // SAFETY: `tag` ensures access to correct variant
669 // SAFETY: non-owned types are copyable
670 std
::mem
::transmute_copy(self)
676 impl<B
> Drop
for KStringInner
<B
> {
678 let tag
= self.tag();
681 // SAFETY: `tag` ensures we are using the right variant
682 std
::mem
::ManuallyDrop
::drop(&mut self.owned
)
689 const LEN_SIZE
: usize = std
::mem
::size_of
::<crate::stack
::Len
>();
692 const TAG_SIZE
: usize = std
::mem
::size_of
::<Tag
>();
695 const PAYLOAD_SIZE
: usize = std
::mem
::size_of
::<crate::backend
::DefaultStr
>();
696 type Payload
= Padding
<PAYLOAD_SIZE
>;
699 const TARGET_SIZE
: usize = std
::mem
::size_of
::<Target
>();
700 type Target
= crate::string
::StdString
;
703 const MAX_CAPACITY
: usize = TARGET_SIZE
- LEN_SIZE
- TAG_SIZE
;
705 // Performance seems to slow down when trying to occupy all of the padding left by `String`'s
706 // discriminant. The question is whether faster len=1-16 "allocations" outweighs going to the heap
709 const ALIGNED_CAPACITY
: usize = PAYLOAD_SIZE
- LEN_SIZE
;
711 #[cfg(feature = "max_inline")]
712 const CAPACITY
: usize = MAX_CAPACITY
;
713 #[cfg(not(feature = "max_inline"))]
714 const CAPACITY
: usize = ALIGNED_CAPACITY
;
716 const PAYLOAD_PAD_SIZE
: usize = TARGET_SIZE
- PAYLOAD_SIZE
- TAG_SIZE
;
717 const INLINE_PAD_SIZE
: usize = TARGET_SIZE
- CAPACITY
- LEN_SIZE
- TAG_SIZE
;
719 #[derive(Copy, Clone)]
723 pad
: Padding
<PAYLOAD_PAD_SIZE
>,
726 static_assertions
::assert_eq_size
!(Target
, TagVariant
);
728 #[derive(Copy, Clone)]
730 struct SingletonVariant
{
731 payload
: &'
static str,
732 pad
: Padding
<PAYLOAD_PAD_SIZE
>,
735 static_assertions
::assert_eq_size
!(Payload
, &'
static str);
736 static_assertions
::assert_eq_size
!(Target
, SingletonVariant
);
738 impl SingletonVariant
{
740 const fn new(payload
: &'
static str) -> Self {
749 impl std
::fmt
::Debug
for SingletonVariant
{
751 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
758 struct OwnedVariant
<B
> {
760 pad
: Padding
<PAYLOAD_PAD_SIZE
>,
763 static_assertions
::assert_eq_size
!(Payload
, crate::backend
::DefaultStr
);
764 static_assertions
::assert_eq_size
!(Target
, OwnedVariant
<crate::backend
::DefaultStr
>);
766 impl<B
> OwnedVariant
<B
> {
768 const fn new(payload
: B
) -> Self {
777 impl<B
: crate::backend
::HeapStr
> std
::fmt
::Debug
for OwnedVariant
<B
> {
779 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
784 #[derive(Copy, Clone)]
786 struct InlineVariant
{
787 payload
: StackString
<CAPACITY
>,
788 pad
: Padding
<INLINE_PAD_SIZE
>,
791 static_assertions
::assert_eq_size
!(Target
, InlineVariant
);
795 const fn new(payload
: StackString
<CAPACITY
>) -> Self {
804 impl std
::fmt
::Debug
for InlineVariant
{
806 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
811 #[derive(Copy, Clone, PartialEq, Eq)]
816 const SINGLETON
: Tag
= Tag(0);
817 const OWNED
: Tag
= Tag(u8::MAX
);
818 const INLINE
: Tag
= Tag(1);
821 const fn is_singleton(self) -> bool
{
822 self.0 == Self::SINGLETON
.0
826 const fn is_owned(self) -> bool
{
827 self.0 == Self::OWNED
.0
831 const fn is_inline(self) -> bool
{
832 !self.is_singleton() && !self.is_owned()
836 #[derive(Copy, Clone)]
838 struct Padding
<const L
: usize>([std
::mem
::MaybeUninit
<u8>; L
]);
840 impl<const L
: usize> Padding
<L
> {
841 const fn new() -> Self {
842 let padding
= unsafe {
843 // SAFETY: Padding, never actually used
844 std
::mem
::MaybeUninit
::uninit().assume_init()
850 impl<const L
: usize> Default
for Padding
<L
> {
851 fn default() -> Self {
863 println
!("KString: {}", std
::mem
::size_of
::<KString
>());