1#![allow(rustdoc::bare_urls)]
2#![doc = include_str!("../README.md")]
3#![allow(unstable_name_collisions)]
4#![no_std]
5
6extern crate alloc;
7
8#[rustversion::before(1.84)]
9use sptr::Strict;
10
11use alloc::{
12 alloc::{alloc, dealloc, Layout},
13 borrow::{Cow, ToOwned},
14 boxed::Box,
15 str::Utf8Error,
16 string::String,
17};
18use core::{
19 cmp::Ordering,
20 fmt,
21 hash::{Hash, Hasher},
22 iter::FromIterator,
23 mem,
24 ops::Deref,
25 ptr,
26 ptr::NonNull,
27 slice, str,
28};
29
30mod vint;
31use crate::vint::VarInt;
32
33#[cfg(feature = "rkyv")]
34mod rkyv;
35
36const HEAP_ALIGN: usize = 4;
37const WIDTH: usize = mem::size_of::<usize>();
38
39#[repr(transparent)]
54pub struct ColdString {
55 encoded: NonNull<u8>,
62}
63
64static EIGHT_NUL: [u8; WIDTH] = [0u8; WIDTH];
65
66impl ColdString {
67 const TAG_MASK: usize = usize::from_ne_bytes(0b11000000usize.to_le_bytes());
68 const INLINE_TAG: usize = usize::from_ne_bytes(0b11111000usize.to_le_bytes());
69 const PTR_TAG: usize = usize::from_ne_bytes(0b10000000usize.to_le_bytes());
70 const LEN_MASK: usize = usize::from_ne_bytes(0b111usize.to_le_bytes());
71 const EIGHT_NUL_MAP: usize = usize::MAX;
72 const ROT: u32 = if cfg!(target_endian = "little") {
73 0
74 } else {
75 8 * (WIDTH - 1) as u32
76 };
77
78 pub fn from_utf8<B: AsRef<[u8]>>(v: B) -> Result<Self, Utf8Error> {
103 Ok(Self::new(str::from_utf8(v.as_ref())?))
104 }
105
106 pub unsafe fn from_utf8_unchecked<B: AsRef<[u8]>>(v: B) -> Self {
127 Self::new(str::from_utf8_unchecked(v.as_ref()))
128 }
129
130 pub fn new<T: AsRef<str>>(x: T) -> Self {
134 let s = x.as_ref();
135 if s.len() <= WIDTH {
136 Self::new_inline(s)
137 } else {
138 Self::new_heap(s)
139 }
140 }
141
142 #[rustversion::attr(since(1.61), const)]
143 #[inline]
144 fn new_eight_nul() -> Self {
145 unsafe { Self::from_inline_buf(Self::EIGHT_NUL_MAP.to_ne_bytes()) }
147 }
148
149 #[inline]
150 fn is_eight_nul(&self) -> bool {
151 self.addr() == Self::EIGHT_NUL_MAP
152 }
153
154 #[inline]
155 const fn inline_buf(s: &str) -> [u8; WIDTH] {
156 debug_assert!(s.len() <= WIDTH);
157 let mut buf = [0u8; WIDTH];
158 if s.len() < WIDTH {
159 let tag =
160 (Self::INLINE_TAG | s.len().rotate_left(Self::ROT)).rotate_right(Self::ROT) as u8;
161 buf[0] = tag;
162 }
163 buf
164 }
165
166 #[rustversion::attr(since(1.61), const)]
168 #[inline]
169 unsafe fn from_inline_buf(b: [u8; WIDTH]) -> Self {
170 let encoded = ptr::null_mut::<u8>().wrapping_add(usize::from_ne_bytes(b));
171 let encoded = NonNull::new_unchecked(encoded);
172 Self { encoded }
173 }
174
175 #[inline]
176 const fn utf8_start(l: usize) -> usize {
177 (l < WIDTH) as usize
178 }
179
180 #[inline]
181 fn new_inline(s: &str) -> Self {
182 if s.as_bytes() == EIGHT_NUL {
183 return Self::new_eight_nul();
184 }
185 let mut buf = Self::inline_buf(s);
186 let start = Self::utf8_start(s.len());
187 buf[start..s.len() + start].copy_from_slice(s.as_bytes());
188 unsafe { Self::from_inline_buf(buf) }
192 }
193
194 #[rustversion::since(1.61)]
210 #[inline]
211 pub const fn new_inline_const(s: &str) -> Self {
212 if s.len() > WIDTH {
213 panic!(
214 "Length for `new_inline_const` must be less than `core::mem::size_of::<usize>()`."
215 );
216 }
217 if s.len() == WIDTH {
218 let bytes = unsafe { *(s.as_bytes() as *const _ as *const [u8; WIDTH]) };
220 let int = usize::from_ne_bytes(bytes);
221 if int == 0 {
222 return Self::new_eight_nul();
223 }
224 }
225 let mut buf = Self::inline_buf(s);
226 let start = Self::utf8_start(s.len());
227 let mut i = 0;
228 while i < s.len() {
229 buf[i + start] = s.as_bytes()[i];
230 i += 1;
231 }
232 unsafe { Self::from_inline_buf(buf) }
236 }
237
238 #[rustversion::attr(since(1.71), const)]
239 #[inline]
240 fn ptr(&self) -> *const u8 {
241 self.encoded.as_ptr()
242 }
243
244 #[inline]
245 fn addr(&self) -> usize {
246 self.ptr().addr()
247 }
248
249 #[inline]
250 fn tag(&self) -> usize {
251 self.addr() & Self::TAG_MASK
252 }
253
254 #[inline]
256 pub fn is_inline(&self) -> bool {
257 self.tag() != Self::PTR_TAG
258 }
259
260 #[inline]
261 fn new_heap(s: &str) -> Self {
262 let len = s.len();
263 let (vint_len, len_buf) = VarInt::write(len as u64);
264 let total = vint_len + len;
265 let layout = Layout::from_size_align(total, HEAP_ALIGN).unwrap();
266
267 unsafe {
268 let ptr = alloc(layout);
270 if ptr.is_null() {
271 alloc::alloc::handle_alloc_error(layout);
272 }
273
274 ptr::copy_nonoverlapping(len_buf.as_ptr(), ptr, vint_len);
276 ptr::copy_nonoverlapping(s.as_ptr(), ptr.add(vint_len), len);
277 let encoded = ptr.map_addr(|addr| {
278 debug_assert!(addr % HEAP_ALIGN == 0);
279 let mut addr = addr.rotate_left(6 + Self::ROT);
280 addr |= Self::PTR_TAG;
281 addr
282 });
283 let encoded = NonNull::new_unchecked(encoded);
285 Self { encoded }
286 }
287 }
288
289 #[inline]
290 fn heap_ptr(&self) -> *const u8 {
291 debug_assert!(!self.is_inline());
292 self.ptr().map_addr(|mut addr| {
293 addr ^= Self::PTR_TAG;
294 let addr = addr.rotate_right(6 + Self::ROT);
295 debug_assert!(addr % HEAP_ALIGN == 0);
296 addr
297 })
298 }
299
300 #[inline]
301 fn inline_len(&self) -> usize {
302 debug_assert!(!self.is_eight_nul());
303 let addr = self.addr();
304 match addr & Self::INLINE_TAG {
305 Self::INLINE_TAG => (addr & Self::LEN_MASK).rotate_right(Self::ROT),
306 _ => WIDTH,
307 }
308 }
309
310 #[inline]
327 pub fn len(&self) -> usize {
328 if self.is_eight_nul() {
329 return WIDTH;
330 } else if self.is_inline() {
331 self.inline_len()
332 } else {
333 unsafe {
334 let ptr = self.heap_ptr();
335 let (len, _) = VarInt::read(ptr);
336 len as usize
337 }
338 }
339 }
340
341 #[allow(unsafe_op_in_unsafe_fn)]
342 #[inline]
343 unsafe fn decode_inline(&self) -> &[u8] {
344 if self.is_eight_nul() {
345 return &EIGHT_NUL;
346 }
347 let len = self.inline_len();
348 let self_bytes_ptr = ptr::addr_of!(self.encoded) as *const u8;
350 let start = Self::utf8_start(len);
351 slice::from_raw_parts(self_bytes_ptr.add(start), len)
352 }
353
354 #[allow(unsafe_op_in_unsafe_fn)]
355 #[inline]
356 unsafe fn decode_heap(&self) -> &[u8] {
357 let ptr = self.heap_ptr();
358 let (len, header) = VarInt::read(ptr);
359 let data = ptr.add(header);
360 slice::from_raw_parts(data, len)
361 }
362
363 #[inline]
377 pub fn as_bytes(&self) -> &[u8] {
378 match self.is_inline() {
379 true => unsafe { self.decode_inline() },
380 false => unsafe { self.decode_heap() },
381 }
382 }
383
384 #[inline]
393 pub fn as_str(&self) -> &str {
394 unsafe { str::from_utf8_unchecked(self.as_bytes()) }
395 }
396
397 #[inline]
406 pub fn is_empty(&self) -> bool {
407 self.len() == 0
408 }
409}
410
411impl Default for ColdString {
412 fn default() -> Self {
413 Self::new_inline("")
414 }
415}
416
417impl Deref for ColdString {
418 type Target = str;
419 fn deref(&self) -> &str {
420 self.as_str()
421 }
422}
423
424impl Drop for ColdString {
425 fn drop(&mut self) {
426 if !self.is_inline() {
427 let ptr = self.heap_ptr();
428 unsafe {
429 let (len, header) = VarInt::read(ptr);
430 let total = header + len;
431 let layout = Layout::from_size_align(total, HEAP_ALIGN).unwrap();
432 dealloc(ptr as *mut u8, layout);
434 }
435 }
436 }
437}
438
439impl Clone for ColdString {
440 fn clone(&self) -> Self {
441 if self.is_inline() {
442 let ptr = self.ptr();
443 let encoded = unsafe { NonNull::new_unchecked(ptr as *mut _) };
444 Self { encoded }
445 } else {
446 Self::new_heap(self.as_str())
447 }
448 }
449}
450
451impl PartialEq for ColdString {
452 fn eq(&self, other: &Self) -> bool {
453 match (self.is_inline(), other.is_inline()) {
454 (true, true) => self.ptr() == other.ptr(),
455 (false, false) => unsafe { self.decode_heap() == other.decode_heap() },
456 _ => false,
457 }
458 }
459}
460
461impl Eq for ColdString {}
462
463impl Hash for ColdString {
464 fn hash<H: Hasher>(&self, state: &mut H) {
465 self.as_str().hash(state)
466 }
467}
468
469impl fmt::Debug for ColdString {
470 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471 fmt::Debug::fmt(self.as_str(), f)
472 }
473}
474
475impl fmt::Display for ColdString {
476 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477 fmt::Display::fmt(self.as_str(), f)
478 }
479}
480
481impl From<&str> for ColdString {
482 fn from(s: &str) -> Self {
483 Self::new(s)
484 }
485}
486
487impl From<String> for ColdString {
488 fn from(s: String) -> Self {
489 Self::new(&s)
490 }
491}
492
493impl From<ColdString> for String {
494 fn from(s: ColdString) -> Self {
495 s.as_str().to_owned()
496 }
497}
498
499impl From<ColdString> for Cow<'_, str> {
500 #[inline]
501 fn from(s: ColdString) -> Self {
502 Self::Owned(s.into())
503 }
504}
505
506impl<'a> From<&'a ColdString> for Cow<'a, str> {
507 #[inline]
508 fn from(s: &'a ColdString) -> Self {
509 Self::Borrowed(s)
510 }
511}
512
513impl<'a> From<Cow<'a, str>> for ColdString {
514 fn from(cow: Cow<'a, str>) -> Self {
515 match cow {
516 Cow::Borrowed(s) => s.into(),
517 Cow::Owned(s) => s.into(),
518 }
519 }
520}
521
522impl From<Box<str>> for ColdString {
523 #[inline]
524 #[track_caller]
525 fn from(b: Box<str>) -> Self {
526 Self::new(&b)
527 }
528}
529
530impl FromIterator<char> for ColdString {
531 fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
532 let s: String = iter.into_iter().collect();
533 ColdString::new(&s)
534 }
535}
536
537unsafe impl Send for ColdString {}
538unsafe impl Sync for ColdString {}
539
540impl core::borrow::Borrow<str> for ColdString {
541 fn borrow(&self) -> &str {
542 self.as_str()
543 }
544}
545
546impl PartialEq<str> for ColdString {
547 fn eq(&self, other: &str) -> bool {
548 if self.is_inline() {
549 unsafe { self.decode_inline() == other.as_bytes() }
550 } else {
551 unsafe { self.decode_heap() == other.as_bytes() }
552 }
553 }
554}
555
556impl PartialEq<ColdString> for str {
557 fn eq(&self, other: &ColdString) -> bool {
558 other.eq(self)
559 }
560}
561
562impl PartialEq<&str> for ColdString {
563 fn eq(&self, other: &&str) -> bool {
564 self.eq(*other)
565 }
566}
567
568impl PartialEq<ColdString> for &str {
569 fn eq(&self, other: &ColdString) -> bool {
570 other.eq(*self)
571 }
572}
573
574impl AsRef<str> for ColdString {
575 #[inline]
576 fn as_ref(&self) -> &str {
577 self.as_str()
578 }
579}
580
581impl AsRef<[u8]> for ColdString {
582 #[inline]
583 fn as_ref(&self) -> &[u8] {
584 self.as_bytes()
585 }
586}
587
588impl Ord for ColdString {
589 fn cmp(&self, other: &Self) -> Ordering {
590 self.as_str().cmp(other.as_str())
591 }
592}
593
594impl PartialOrd for ColdString {
595 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
596 self.as_str().partial_cmp(other.as_str())
597 }
598}
599
600impl alloc::str::FromStr for ColdString {
601 type Err = core::convert::Infallible;
602 fn from_str(s: &str) -> Result<ColdString, Self::Err> {
603 Ok(ColdString::new(s))
604 }
605}
606
607#[cfg(feature = "serde")]
608impl serde::Serialize for ColdString {
609 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
610 serializer.serialize_str(self.as_str())
611 }
612}
613
614#[cfg(feature = "serde")]
615impl<'de> serde::Deserialize<'de> for ColdString {
616 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
617 let s = String::deserialize(d)?;
618 Ok(ColdString::new(&s))
619 }
620}
621
622#[cfg(all(test, feature = "serde"))]
623mod serde_tests {
624 use super::*;
625 use serde_test::{assert_tokens, Token};
626
627 #[test]
628 fn test_serde_cold_string_inline() {
629 let cs = ColdString::new("ferris");
630 assert_tokens(&cs, &[Token::Str("ferris")]);
631 }
632
633 #[test]
634 fn test_serde_cold_string_heap() {
635 let long_str = "This is a significantly longer string for heap testing";
636 let cs = ColdString::new(long_str);
637 assert_tokens(&cs, &[Token::Str(long_str)]);
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644 use core::hash::BuildHasher;
645 use hashbrown::hash_map::DefaultHashBuilder;
646
647 #[test]
648 fn test_layout() {
649 assert_eq!(mem::size_of::<ColdString>(), mem::size_of::<usize>());
650 }
651
652 #[test]
653 fn test_default() {
654 assert!(ColdString::default().is_empty());
655 assert_eq!(ColdString::default().len(), 0);
656 assert_eq!(ColdString::default(), "");
657 assert_eq!(ColdString::default(), ColdString::new(""));
658 }
659
660 fn assert_correct(s: &str) {
661 let cs = ColdString::new(s);
662 assert_eq!(s.len() <= mem::size_of::<usize>(), cs.is_inline());
663 assert_eq!(cs.len(), s.len());
664 assert_eq!(cs.as_bytes(), s.as_bytes());
665 assert_eq!(cs.as_str(), s);
666 assert_eq!(cs.clone(), cs);
667 let bh = DefaultHashBuilder::new();
668 let mut hasher1 = bh.build_hasher();
669 cs.hash(&mut hasher1);
670 let mut hasher2 = bh.build_hasher();
671 cs.clone().hash(&mut hasher2);
672 assert_eq!(hasher1.finish(), hasher2.finish());
673 assert_eq!(cs, s);
674 assert_eq!(s, cs);
675 assert_eq!(cs, *s);
676 assert_eq!(*s, cs);
677 let opt_s = Some(cs.clone());
678 assert_eq!(opt_s, Some(ColdString::new(s)));
679 assert!(opt_s != None);
680 }
681
682 #[test]
683 fn it_works() {
684 for s in [
685 "1",
686 "12",
687 "123",
688 "1234",
689 "12345",
690 "123456",
691 "1234567",
692 "12345678",
693 "123456789",
694 str::from_utf8(&[240, 159, 146, 150]).unwrap(),
695 "✅",
696 "❤️",
697 "🦀💯",
698 "🦀",
699 "💯",
700 "abcd",
701 "test",
702 "",
703 "\0",
704 "\0\0",
705 "\0\0\0",
706 "\0\0\0\0",
707 "\0\0\0\0\0\0\0",
708 "\0\0\0\0\0\0\0\0",
709 "1234567",
710 "12345678",
711 "longer test",
712 str::from_utf8(&[103, 39, 240, 145, 167, 156, 194, 165]).unwrap(),
713 "AaAa0 ® ",
714 str::from_utf8(&[240, 158, 186, 128, 240, 145, 143, 151]).unwrap(),
715 ] {
716 assert_correct(s);
717 }
718 }
719
720 fn char_from_leading_byte(b: u8) -> Option<char> {
721 match b {
722 0x00..=0x7F => Some(b as char),
723 0xC2..=0xDF => str::from_utf8(&[b, 0x91]).unwrap().chars().next(),
724 0xE0 => str::from_utf8(&[b, 0xA0, 0x91]).unwrap().chars().next(),
725 0xE1..=0xEC | 0xEE..=0xEF => str::from_utf8(&[b, 0x91, 0xA5]).unwrap().chars().next(),
726 0xED => str::from_utf8(&[b, 0x80, 0x91]).unwrap().chars().next(),
727 0xF0 => str::from_utf8(&[b, 0x90, 0x91, 0xA5])
728 .unwrap()
729 .chars()
730 .next(),
731 0xF1..=0xF3 => str::from_utf8(&[b, 0x91, 0xA5, 0x82])
732 .unwrap()
733 .chars()
734 .next(),
735 0xF4 => str::from_utf8(&[b, 0x80, 0x91, 0x82])
736 .unwrap()
737 .chars()
738 .next(),
739 _ => None,
740 }
741 }
742
743 #[test]
744 fn test_edges() {
745 let width = mem::size_of::<usize>();
746 for len in [width - 1, width, width + 1] {
747 for first_byte in 0u8..=255 {
748 let first_char = match char_from_leading_byte(first_byte) {
749 Some(c) => c,
750 None => continue,
751 };
752
753 let mut s = String::with_capacity(len);
754 s.push(first_char);
755
756 while s.len() < len {
757 let c = core::char::from_digit((len - s.len()) as u32, 10).unwrap();
758 s.push(c);
759 }
760
761 assert_correct(&s);
762 }
763 }
764 }
765
766 #[test]
767 fn test_unaligned_placement() {
768 for s_content in ["torture", "tor", "tortures", "tort", "torture torture"] {
769 let mut buffer = [0u8; 32];
770 for offset in 0..8 {
771 unsafe {
772 let dst = buffer.as_mut_ptr().add(offset) as *mut ColdString;
773 let s = ColdString::new(s_content);
774 ptr::write_unaligned(dst, s);
775 let recovered = ptr::read_unaligned(dst);
776 assert_eq!(recovered.as_str(), s_content);
777 }
778 }
779 }
780 }
781
782 #[test]
783 fn ensure_zero_repr() {
784 assert!(str::from_utf8(&ColdString::EIGHT_NUL_MAP.to_ne_bytes()).is_err());
785 }
786
787 #[test]
788 fn test_const_8nul_vs_non_const() {
789 let nul8 = str::from_utf8(&EIGHT_NUL).unwrap();
790 let const8 = ColdString::new_inline_const(nul8);
791 let non_const = ColdString::new(nul8);
792 let cloned = non_const.clone();
793 assert_eq!(const8.ptr(), non_const.ptr());
794 assert_eq!(const8.ptr(), cloned.ptr());
795 assert_eq!(
797 &const8.as_str().as_bytes()[0] as *const u8,
798 (&EIGHT_NUL) as *const u8
799 );
800 }
801}