1#![allow(rustdoc::bare_urls)]
2#![doc = include_str!("../README.md")]
3#![allow(unknown_lints, unexpected_cfgs)]
4#![allow(unstable_name_collisions)]
5#![deny(unused_imports)]
6#![no_std]
7
8extern crate alloc;
9
10#[cfg(test)]
11extern crate std;
12
13use alloc::{
14 borrow::{Cow, ToOwned},
15 boxed::Box,
16 str::Utf8Error,
17 string::String,
18};
19use core::{
20 cmp::Ordering,
21 fmt,
22 hash::{Hash, Hasher},
23 iter::FromIterator,
24 ops::Deref,
25 str,
26};
27
28#[cfg(test)]
29use core::{mem, ptr};
30
31mod arc;
32mod encoded;
33mod heap;
34mod vint;
35
36pub use crate::arc::ArcColdString;
37pub use crate::arc::ArcColdString16;
38pub use crate::arc::ArcColdString32;
39pub use crate::arc::ArcColdString8;
40use crate::encoded::Encoded;
41
42#[cfg(feature = "rkyv")]
43mod rkyv;
44
45#[repr(transparent)]
60pub struct ColdString {
61 encoded: Encoded<()>,
62}
63
64impl ColdString {
65 pub fn from_utf8<B: AsRef<[u8]>>(v: B) -> Result<Self, Utf8Error> {
90 Ok(Self::new(str::from_utf8(v.as_ref())?))
91 }
92
93 pub unsafe fn from_utf8_unchecked<B: AsRef<[u8]>>(v: B) -> Self {
118 Self::new(str::from_utf8_unchecked(v.as_ref()))
119 }
120
121 pub fn new<T: AsRef<str>>(x: T) -> Self {
125 let s = x.as_ref();
126 Self {
127 encoded: Encoded::new(s, ()),
128 }
129 }
130
131 #[rustversion::since(1.61)]
147 #[inline]
148 pub const fn new_inline_const(s: &str) -> Self {
149 Self {
150 encoded: Encoded::new_inline_const(s),
151 }
152 }
153
154 #[inline]
156 pub fn is_inline(&self) -> bool {
157 self.encoded.is_inline()
158 }
159
160 #[inline]
177 pub fn len(&self) -> usize {
178 self.encoded.len()
179 }
180
181 #[inline]
195 pub fn as_bytes(&self) -> &[u8] {
196 self.encoded.as_bytes()
197 }
198
199 #[inline]
208 pub fn as_str(&self) -> &str {
209 unsafe { str::from_utf8_unchecked(self.as_bytes()) }
210 }
211
212 #[inline]
221 pub fn is_empty(&self) -> bool {
222 self.len() == 0
223 }
224}
225
226impl Default for ColdString {
227 fn default() -> Self {
228 Self::new("")
229 }
230}
231
232impl Deref for ColdString {
233 type Target = str;
234 fn deref(&self) -> &str {
235 self.as_str()
236 }
237}
238
239impl Drop for ColdString {
240 fn drop(&mut self) {
241 if !self.is_inline() {
242 unsafe { self.encoded.deallocate() }
244 }
245 }
246}
247
248impl Clone for ColdString {
249 fn clone(&self) -> Self {
250 if self.is_inline() {
251 Self {
252 encoded: self.encoded,
253 }
254 } else {
255 Self::new(self.as_str())
256 }
257 }
258}
259
260impl PartialEq for ColdString {
261 fn eq(&self, other: &Self) -> bool {
262 if self.is_inline() && other.is_inline() {
263 self.encoded.addr() == other.encoded.addr()
264 } else if !self.is_inline() && !other.is_inline() {
265 if self.encoded.heap_prefix() != other.encoded.heap_prefix() {
266 false
267 } else {
268 self.as_bytes() == other.as_bytes()
269 }
270 } else {
271 false
272 }
273 }
274}
275
276impl Eq for ColdString {}
277
278impl Hash for ColdString {
279 fn hash<H: Hasher>(&self, state: &mut H) {
280 self.as_str().hash(state)
281 }
282}
283
284impl fmt::Debug for ColdString {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 fmt::Debug::fmt(self.as_str(), f)
287 }
288}
289
290impl fmt::Display for ColdString {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 fmt::Display::fmt(self.as_str(), f)
293 }
294}
295
296impl From<&str> for ColdString {
297 fn from(s: &str) -> Self {
298 Self::new(s)
299 }
300}
301
302impl From<String> for ColdString {
303 fn from(s: String) -> Self {
304 Self::new(&s)
305 }
306}
307
308impl From<ColdString> for String {
309 fn from(s: ColdString) -> Self {
310 s.as_str().to_owned()
311 }
312}
313
314impl From<ColdString> for Cow<'_, str> {
315 #[inline]
316 fn from(s: ColdString) -> Self {
317 Self::Owned(s.into())
318 }
319}
320
321impl<'a> From<&'a ColdString> for Cow<'a, str> {
322 #[inline]
323 fn from(s: &'a ColdString) -> Self {
324 Self::Borrowed(s)
325 }
326}
327
328impl<'a> From<Cow<'a, str>> for ColdString {
329 fn from(cow: Cow<'a, str>) -> Self {
330 Self::new(cow)
331 }
332}
333
334impl From<Box<str>> for ColdString {
335 #[inline]
336 #[track_caller]
337 fn from(b: Box<str>) -> Self {
338 Self::new(&b)
339 }
340}
341
342impl FromIterator<char> for ColdString {
343 fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
344 Self::new(iter.into_iter().collect::<String>())
345 }
346}
347
348unsafe impl Send for ColdString {}
349unsafe impl Sync for ColdString {}
350
351impl core::borrow::Borrow<str> for ColdString {
352 fn borrow(&self) -> &str {
353 self.as_str()
354 }
355}
356
357impl PartialEq<str> for ColdString {
358 fn eq(&self, other: &str) -> bool {
359 self.as_str() == other
360 }
361}
362
363impl PartialEq<ColdString> for str {
364 fn eq(&self, other: &ColdString) -> bool {
365 other.eq(self)
366 }
367}
368
369impl PartialEq<&str> for ColdString {
370 fn eq(&self, other: &&str) -> bool {
371 self.eq(*other)
372 }
373}
374
375impl PartialEq<ColdString> for &str {
376 fn eq(&self, other: &ColdString) -> bool {
377 other.eq(*self)
378 }
379}
380
381impl AsRef<str> for ColdString {
382 #[inline]
383 fn as_ref(&self) -> &str {
384 self.as_str()
385 }
386}
387
388impl AsRef<[u8]> for ColdString {
389 #[inline]
390 fn as_ref(&self) -> &[u8] {
391 self.as_bytes()
392 }
393}
394
395impl Ord for ColdString {
396 fn cmp(&self, other: &Self) -> Ordering {
397 self.as_str().cmp(other.as_str())
398 }
399}
400
401impl PartialOrd for ColdString {
402 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
403 Some(self.cmp(other))
404 }
405}
406
407impl alloc::str::FromStr for ColdString {
408 type Err = core::convert::Infallible;
409 fn from_str(s: &str) -> Result<ColdString, Self::Err> {
410 Ok(ColdString::new(s))
411 }
412}
413
414#[cfg(feature = "serde")]
415impl serde::Serialize for ColdString {
416 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
417 serializer.serialize_str(self.as_str())
418 }
419}
420
421#[cfg(feature = "serde")]
422impl<'de> serde::Deserialize<'de> for ColdString {
423 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
424 let s = String::deserialize(d)?;
425 Ok(ColdString::new(&s))
426 }
427}
428
429#[cfg(test)]
430trait TestString:
431 Clone + Default + fmt::Debug + Eq + Hash + PartialEq<str> + for<'a> PartialEq<&'a str>
432{
433 fn new(s: &str) -> Self;
434 fn from_utf8(bytes: &[u8]) -> Result<Self, Utf8Error>;
435 fn new_inline(s: &str) -> Self;
436 fn is_inline(&self) -> bool;
437 fn is_empty(&self) -> bool;
438 fn len(&self) -> usize;
439 fn as_bytes(&self) -> &[u8];
440 fn as_str(&self) -> &str;
441 fn encoded_addr(&self) -> usize;
442}
443
444#[cfg(test)]
445impl TestString for ColdString {
446 fn new(s: &str) -> Self {
447 Self::new(s)
448 }
449
450 fn from_utf8(bytes: &[u8]) -> Result<Self, Utf8Error> {
451 Self::from_utf8(bytes)
452 }
453
454 fn new_inline(s: &str) -> Self {
455 Self::new_inline_const(s)
456 }
457
458 fn is_inline(&self) -> bool {
459 self.is_inline()
460 }
461
462 fn is_empty(&self) -> bool {
463 self.is_empty()
464 }
465
466 fn len(&self) -> usize {
467 self.len()
468 }
469
470 fn as_bytes(&self) -> &[u8] {
471 self.as_bytes()
472 }
473
474 fn as_str(&self) -> &str {
475 self.as_str()
476 }
477
478 fn encoded_addr(&self) -> usize {
479 self.encoded.addr()
480 }
481}
482
483#[cfg(test)]
484impl<A: arc::RefCount> TestString for arc::ArcColdStringInner<A> {
485 fn new(s: &str) -> Self {
486 Self::new(s)
487 }
488
489 fn from_utf8(bytes: &[u8]) -> Result<Self, Utf8Error> {
490 Self::from_utf8(bytes)
491 }
492
493 fn new_inline(s: &str) -> Self {
494 Self::new_inline_const(s)
495 }
496
497 fn is_inline(&self) -> bool {
498 self.is_inline()
499 }
500
501 fn is_empty(&self) -> bool {
502 self.is_empty()
503 }
504
505 fn len(&self) -> usize {
506 self.len()
507 }
508
509 fn as_bytes(&self) -> &[u8] {
510 self.as_bytes()
511 }
512
513 fn as_str(&self) -> &str {
514 self.as_str()
515 }
516
517 fn encoded_addr(&self) -> usize {
518 self.encoded_addr()
519 }
520}
521
522#[cfg(test)]
523macro_rules! each_string {
524 ($test:ident $(, $arg:expr)*) => {
525 $test::<ColdString>($($arg),*);
526 $test::<ArcColdString>($($arg),*);
527 $test::<ArcColdString8>($($arg),*);
528 $test::<ArcColdString16>($($arg),*);
529 $test::<ArcColdString32>($($arg),*);
530 };
531}
532
533#[cfg(all(test, feature = "serde"))]
534mod serde_tests {
535 use super::*;
536 use serde_test::{assert_tokens, Token};
537
538 fn assert_serde<T>(s: &'static str)
539 where
540 T: TestString + serde::Serialize + for<'de> serde::Deserialize<'de>,
541 {
542 assert_tokens(&T::new(s), &[Token::Str(s)]);
543 }
544
545 #[test]
546 fn test_serde_cold_string_inline() {
547 each_string!(assert_serde, "ferris");
548 }
549
550 #[test]
551 fn test_serde_cold_string_heap() {
552 let long_str = "This is a significantly longer string for heap testing";
553 each_string!(assert_serde, long_str);
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use core::hash::BuildHasher;
561 use hashbrown::hash_map::DefaultHashBuilder;
562
563 fn assert_layout<T: TestString>() {
564 assert_eq!(mem::size_of::<T>(), mem::size_of::<usize>());
565 assert_eq!(mem::size_of::<Option<T>>(), mem::size_of::<T>());
566 }
567
568 #[test]
569 fn test_layout() {
570 each_string!(assert_layout);
571 }
572
573 fn assert_default<T: TestString>() {
574 assert!(T::default().is_empty());
575 assert_eq!(T::default().len(), 0);
576 assert_eq!(T::default(), "");
577 assert_eq!(T::default(), T::new(""));
578 }
579
580 #[test]
581 fn test_default() {
582 each_string!(assert_default);
583 }
584
585 fn assert_utf8_validation<T: TestString>() {
586 for valid in ["", "🦀", "valid UTF-8 🦀 longer than one word"] {
587 assert_eq!(T::from_utf8(valid.as_bytes()).unwrap().as_str(), valid);
588 }
589
590 for invalid in [
591 &[0x80][..],
592 &[0xff][..],
593 &[0xc0, 0x80][..],
594 &[0xe2, 0x82][..],
595 ] {
596 assert!(T::from_utf8(invalid).is_err());
597 }
598 }
599
600 #[test]
601 fn test_utf8_validation() {
602 each_string!(assert_utf8_validation);
603 }
604
605 fn assert_correct<T: TestString>(s: &str)
606 where
607 str: PartialEq<T>,
608 for<'a> &'a str: PartialEq<T>,
609 {
610 let cs = T::new(s);
611 assert_eq!(s.len() <= mem::size_of::<usize>(), cs.is_inline());
612 assert_eq!(cs.len(), s.len(), "error for: {:?}", s);
613 assert_eq!(cs.as_bytes(), s.as_bytes());
614 assert_eq!(cs.as_str().as_bytes(), s.as_bytes());
615 assert_eq!(cs.clone(), cs);
616 let bh = DefaultHashBuilder::new();
617 let mut hasher1 = bh.build_hasher();
618 cs.hash(&mut hasher1);
619 let mut hasher2 = bh.build_hasher();
620 cs.clone().hash(&mut hasher2);
621 assert_eq!(hasher1.finish(), hasher2.finish());
622 assert_eq!(cs, s);
623 assert_eq!(s, cs);
624 assert_eq!(cs, *s);
625 assert_eq!(*s, cs);
626 assert_eq!(cs, cs.clone());
627 assert!(cs != T::new("unused-qwerty"));
628 let opt_s = Some(cs.clone());
629 assert_eq!(opt_s, Some(T::new(s)));
630 assert!(opt_s.is_some());
631 }
632
633 #[test]
634 fn it_works() {
635 for s in [
636 "1",
637 "12",
638 "123",
639 "1234",
640 "12345",
641 "123456",
642 "1234567",
643 "12345678",
644 "123456789",
645 str::from_utf8(&[240, 159, 146, 150]).unwrap(),
646 "✅",
647 "❤️",
648 "🦀💯",
649 "🦀",
650 "💯",
651 "abcd",
652 "test",
653 "",
654 "\0",
655 "\0\0",
656 "\0\0\0",
657 "\0\0\0\0",
658 "\0\0\0\0\0\0\0",
659 "\0\0\0\0\0\0\0\0",
660 "1234567",
661 "12345678",
662 "longer test",
663 str::from_utf8(&[103, 39, 240, 145, 167, 156, 194, 165]).unwrap(),
664 "AaAa0 ® ",
665 str::from_utf8(&[240, 158, 186, 128, 240, 145, 143, 151]).unwrap(),
666 ] {
667 each_string!(assert_correct, s);
668 }
669 }
670
671 fn char_from_leading_byte(b: u8) -> Option<char> {
672 match b {
673 0x00..=0x7F => Some(b as char),
674 0xC2..=0xDF => str::from_utf8(&[b, 0x91]).unwrap().chars().next(),
675 0xE0 => str::from_utf8(&[b, 0xA0, 0x91]).unwrap().chars().next(),
676 0xE1..=0xEC | 0xEE..=0xEF => str::from_utf8(&[b, 0x91, 0xA5]).unwrap().chars().next(),
677 0xED => str::from_utf8(&[b, 0x80, 0x91]).unwrap().chars().next(),
678 0xF0 => str::from_utf8(&[b, 0x90, 0x91, 0xA5])
679 .unwrap()
680 .chars()
681 .next(),
682 0xF1..=0xF3 => str::from_utf8(&[b, 0x91, 0xA5, 0x82])
683 .unwrap()
684 .chars()
685 .next(),
686 0xF4 => str::from_utf8(&[b, 0x80, 0x91, 0x82])
687 .unwrap()
688 .chars()
689 .next(),
690 _ => None,
691 }
692 }
693
694 #[test]
695 fn test_edges() {
696 let width = mem::size_of::<usize>();
697 for len in [width - 1, width, width + 1] {
698 for first_byte in 0u8..=255 {
699 let first_char = match char_from_leading_byte(first_byte) {
700 Some(c) => c,
701 None => continue,
702 };
703
704 let mut s = String::with_capacity(len);
705 s.push(first_char);
706
707 while s.len() < len {
708 let c = core::char::from_digit((len - s.len()) as u32, 10).unwrap();
709 s.push(c);
710 }
711
712 each_string!(assert_correct, &s);
713 }
714 }
715 }
716
717 fn assert_unaligned_placement<T: TestString>() {
718 for s_content in ["torture", "tor", "tortures", "tort", "torture torture"] {
719 let mut buffer = [0u8; 32];
720 for offset in 0..8 {
721 unsafe {
722 let dst = buffer.as_mut_ptr().add(offset).cast::<T>();
723 let s = T::new(s_content);
724 ptr::write_unaligned(dst, s);
725 let recovered = ptr::read_unaligned(dst);
726 assert_eq!(recovered.as_str(), s_content);
727 }
728 }
729 }
730 }
731
732 #[test]
733 fn test_unaligned_placement() {
734 each_string!(assert_unaligned_placement);
735 }
736
737 #[test]
738 fn ensure_zero_repr() {
739 assert!(str::from_utf8(&Encoded::<()>::WORD_NUL_MAP.to_ne_bytes()).is_err());
740 }
741
742 fn assert_const_word_nul<T: TestString>() {
743 let nul = str::from_utf8(&encoded::WORD_NUL).unwrap();
744 let const_value = T::new_inline(nul);
745 let non_const = T::new(nul);
746 let cloned = non_const.clone();
747 assert_eq!(const_value.encoded_addr(), non_const.encoded_addr());
748 assert_eq!(const_value.encoded_addr(), cloned.encoded_addr());
749 assert_eq!(
751 &const_value.as_str().as_bytes()[0] as *const u8,
752 (&encoded::WORD_NUL) as *const u8
753 );
754 }
755
756 #[test]
757 fn test_const_word_nul() {
758 each_string!(assert_const_word_nul);
759 }
760}