1use std::borrow::Borrow;
46use std::cmp::Ordering;
47use std::fmt;
48use std::hash::{Hash, Hasher};
49use std::mem;
50use std::ops::Deref;
51use std::ptr;
52use std::sync::Arc;
53
54pub const MAX_INLINE_LEN: usize = 15;
56
57#[cfg(target_pointer_width = "64")]
61mod ptr_layout {
62 pub const OFFSET: usize = 7;
64 pub const SIZE: usize = 8;
66 pub type Bytes = [u8; 8];
68}
69
70#[cfg(target_pointer_width = "32")]
71mod ptr_layout {
72 pub const OFFSET: usize = 11;
74 pub const SIZE: usize = 4;
76 pub type Bytes = [u8; 4];
78}
79
80#[repr(u8)]
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub enum StringTag {
86 Inline0 = 0,
88 Inline1 = 1,
89 Inline2 = 2,
90 Inline3 = 3,
91 Inline4 = 4,
92 Inline5 = 5,
93 Inline6 = 6,
94 Inline7 = 7,
95 Inline8 = 8,
96 Inline9 = 9,
97 Inline10 = 10,
98 Inline11 = 11,
99 Inline12 = 12,
100 Inline13 = 13,
101 Inline14 = 14,
102 Inline15 = 15,
103 Heap = 16,
105 }
107
108impl StringTag {
109 #[inline]
111 pub const fn is_inline(self) -> bool {
112 (self as u8) <= 15
113 }
114
115 #[inline]
117 pub const fn is_heap(self) -> bool {
118 (self as u8) == 16
119 }
120
121 #[inline]
123 pub const fn inline_len(self) -> Option<usize> {
124 let v = self as u8;
125 if v <= 15 {
126 Some(v as usize)
127 } else {
128 None
129 }
130 }
131
132 #[inline]
134 const fn inline(len: usize) -> Self {
135 debug_assert!(len <= 15);
136 unsafe { std::mem::transmute(len as u8) }
138 }
139}
140
141#[repr(C, align(8))]
147pub struct SmartString {
148 tag: StringTag,
150 data: [u8; 15],
154}
155
156impl SmartString {
157 #[inline]
159 pub fn new(s: &str) -> Self {
160 let len = s.len();
161 if len <= MAX_INLINE_LEN {
162 let mut data = [0u8; 15];
163 data[..len].copy_from_slice(s.as_bytes());
164 SmartString {
165 tag: StringTag::inline(len),
166 data,
167 }
168 } else {
169 Self::new_heap(s.to_string())
170 }
171 }
172
173 #[inline]
175 fn new_heap(s: String) -> Self {
176 Self::new_heap_arc(Arc::<str>::from(s))
177 }
178
179 #[inline]
180 fn new_heap_arc(arc: Arc<str>) -> Self {
181 let len = arc.len();
182 let mut data = [0u8; 15];
183 Self::encode_heap_len(&mut data, len);
184 let ptr = Arc::into_raw(arc) as *const u8 as usize;
185 let end = ptr_layout::OFFSET + ptr_layout::SIZE;
186 data[ptr_layout::OFFSET..end].copy_from_slice(&ptr.to_ne_bytes());
187 SmartString {
188 tag: StringTag::Heap,
189 data,
190 }
191 }
192
193 #[inline]
194 fn encode_heap_len(data: &mut [u8; 15], len: usize) {
195 let bytes = len.to_le_bytes();
196 let stored = ptr_layout::OFFSET.min(bytes.len());
197 assert!(
198 bytes[stored..].iter().all(|byte| *byte == 0),
199 "SmartString heap length exceeds compact representation"
200 );
201 data[..stored].copy_from_slice(&bytes[..stored]);
202 }
203
204 #[inline(always)]
205 fn heap_len(&self) -> usize {
206 let mut bytes = [0u8; mem::size_of::<usize>()];
207 let stored = ptr_layout::OFFSET.min(bytes.len());
208 bytes[..stored].copy_from_slice(&self.data[..stored]);
209 usize::from_le_bytes(bytes)
210 }
211
212 #[inline]
214 pub fn const_new(s: &str) -> Self {
215 Self::new(s)
216 }
217
218 #[inline]
220 pub fn from_string(s: String) -> Self {
221 let len = s.len();
222 if len <= MAX_INLINE_LEN {
223 let mut data = [0u8; 15];
224 data[..len].copy_from_slice(s.as_bytes());
225 SmartString {
226 tag: StringTag::inline(len),
227 data,
228 }
229 } else {
230 Self::new_heap(s)
231 }
232 }
233
234 #[inline]
236 pub fn from_string_shared(s: String) -> Self {
237 Self::from_string(s)
239 }
240
241 #[inline(always)]
243 pub fn as_str(&self) -> &str {
244 if let Some(len) = self.tag.inline_len() {
245 unsafe { std::str::from_utf8_unchecked(&self.data[..len]) }
248 } else {
249 unsafe { &*self.get_arc_ptr() }
252 }
253 }
254
255 #[inline(always)]
257 pub fn len(&self) -> usize {
258 if let Some(len) = self.tag.inline_len() {
259 len
260 } else {
261 self.as_str().len()
262 }
263 }
264
265 #[inline]
266 pub fn is_empty(&self) -> bool {
267 self.len() == 0
268 }
269
270 #[inline]
271 pub fn is_inline(&self) -> bool {
272 self.tag.is_inline()
273 }
274
275 #[inline]
276 pub fn is_heap(&self) -> bool {
277 self.tag.is_heap()
278 }
279
280 #[inline]
282 fn get_arc_ptr(&self) -> *const str {
283 debug_assert!(self.tag.is_heap());
284 let end = ptr_layout::OFFSET + ptr_layout::SIZE;
285 let ptr_bytes: ptr_layout::Bytes = self.data[ptr_layout::OFFSET..end].try_into().unwrap();
286 let data_ptr = usize::from_ne_bytes(ptr_bytes) as *const u8;
287 ptr::slice_from_raw_parts(data_ptr, self.heap_len()) as *const str
288 }
289
290 #[inline]
297 pub fn push(&mut self, ch: char) {
298 let ch_len = ch.len_utf8();
299
300 if let Some(current_len) = self.tag.inline_len() {
301 if current_len + ch_len <= MAX_INLINE_LEN {
302 ch.encode_utf8(&mut self.data[current_len..]);
304 self.tag = StringTag::inline(current_len + ch_len);
305 return;
306 }
307 }
308
309 let mut s = self.as_str().to_string();
311 s.push(ch);
312 *self = Self::from_string(s);
313 }
314
315 #[inline]
317 pub fn push_str(&mut self, string: &str) {
318 if string.is_empty() {
319 return;
320 }
321
322 let add_len = string.len();
323
324 if let Some(current_len) = self.tag.inline_len() {
325 if current_len + add_len <= MAX_INLINE_LEN {
326 self.data[current_len..current_len + add_len].copy_from_slice(string.as_bytes());
328 self.tag = StringTag::inline(current_len + add_len);
329 return;
330 }
331 }
332
333 let mut s = self.as_str().to_string();
335 s.push_str(string);
336 *self = Self::from_string(s);
337 }
338
339 #[inline]
340 pub fn to_lowercase(&self) -> SmartString {
341 SmartString::from_string(self.as_str().to_lowercase())
342 }
343
344 #[inline]
345 pub fn to_uppercase(&self) -> SmartString {
346 SmartString::from_string(self.as_str().to_uppercase())
347 }
348
349 #[inline]
350 pub fn is_ascii(&self) -> bool {
351 self.as_str().is_ascii()
352 }
353
354 #[inline]
355 pub fn make_ascii_uppercase(&mut self) {
356 if let Some(len) = self.tag.inline_len() {
357 self.data[..len].make_ascii_uppercase();
358 } else {
359 let mut s = self.as_str().to_string();
360 s.make_ascii_uppercase();
361 *self = Self::from_string(s);
362 }
363 }
364
365 #[inline]
366 pub fn make_ascii_lowercase(&mut self) {
367 if let Some(len) = self.tag.inline_len() {
368 self.data[..len].make_ascii_lowercase();
369 } else {
370 let mut s = self.as_str().to_string();
371 s.make_ascii_lowercase();
372 *self = Self::from_string(s);
373 }
374 }
375
376 #[inline]
377 pub fn into_string(self) -> String {
378 if self.tag.is_inline() {
379 self.as_str().to_owned()
380 } else {
381 let ptr = self.get_arc_ptr();
383 std::mem::forget(self); let arc = unsafe { Arc::from_raw(ptr) };
387 arc.as_ref().to_owned()
388 }
389 }
390
391 #[inline]
392 pub fn concat(a: &str, b: &str) -> SmartString {
393 let total_len = a.len() + b.len();
394 if total_len <= MAX_INLINE_LEN {
395 let mut data = [0u8; 15];
396 data[..a.len()].copy_from_slice(a.as_bytes());
397 data[a.len()..total_len].copy_from_slice(b.as_bytes());
398 SmartString {
399 tag: StringTag::inline(total_len),
400 data,
401 }
402 } else {
403 let mut s = String::with_capacity(total_len);
404 s.push_str(a);
405 s.push_str(b);
406 SmartString::from_string(s)
407 }
408 }
409
410 #[inline]
416 pub unsafe fn build_inline<F>(total_len: usize, builder: F) -> Option<SmartString>
417 where
418 F: FnOnce(&mut [u8]),
419 {
420 if total_len <= MAX_INLINE_LEN {
421 let mut data = [0u8; 15];
422 builder(&mut data[..total_len]);
423 Some(SmartString {
424 tag: StringTag::inline(total_len),
425 data,
426 })
427 } else {
428 None
429 }
430 }
431}
432
433impl Clone for SmartString {
434 #[inline]
435 fn clone(&self) -> Self {
436 if self.tag.is_heap() {
437 unsafe {
440 Arc::increment_strong_count(self.get_arc_ptr());
441 }
442 }
443 SmartString {
445 tag: self.tag,
446 data: self.data,
447 }
448 }
449}
450
451impl Drop for SmartString {
452 #[inline]
453 fn drop(&mut self) {
454 if self.tag.is_heap() {
455 let ptr = self.get_arc_ptr();
457 unsafe {
459 Arc::from_raw(ptr);
460 }
461 }
462 }
463}
464
465impl Default for SmartString {
466 #[inline]
467 fn default() -> Self {
468 SmartString {
469 tag: StringTag::Inline0,
470 data: [0u8; 15],
471 }
472 }
473}
474
475impl Deref for SmartString {
476 type Target = str;
477 #[inline]
478 fn deref(&self) -> &str {
479 self.as_str()
480 }
481}
482
483impl AsRef<str> for SmartString {
484 #[inline]
485 fn as_ref(&self) -> &str {
486 self.as_str()
487 }
488}
489
490impl Borrow<str> for SmartString {
491 #[inline]
492 fn borrow(&self) -> &str {
493 self.as_str()
494 }
495}
496
497impl From<&str> for SmartString {
498 #[inline]
499 fn from(s: &str) -> Self {
500 SmartString::new(s)
501 }
502}
503
504impl From<String> for SmartString {
505 #[inline]
506 fn from(s: String) -> Self {
507 SmartString::from_string(s)
508 }
509}
510
511impl From<Arc<str>> for SmartString {
512 #[inline]
513 fn from(arc: Arc<str>) -> Self {
514 if arc.len() <= MAX_INLINE_LEN {
515 SmartString::new(&arc)
516 } else {
517 SmartString::new_heap_arc(arc)
518 }
519 }
520}
521
522impl fmt::Debug for SmartString {
523 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
524 fmt::Debug::fmt(self.as_str(), f)
525 }
526}
527
528impl fmt::Display for SmartString {
529 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530 fmt::Display::fmt(self.as_str(), f)
531 }
532}
533
534impl PartialEq for SmartString {
535 #[inline]
536 fn eq(&self, other: &Self) -> bool {
537 self.as_str() == other.as_str()
538 }
539}
540
541impl Eq for SmartString {}
542
543impl PartialOrd for SmartString {
544 #[inline]
545 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
546 Some(self.cmp(other))
547 }
548}
549
550impl Ord for SmartString {
551 #[inline]
552 fn cmp(&self, other: &Self) -> Ordering {
553 self.as_str().cmp(other.as_str())
554 }
555}
556
557impl Hash for SmartString {
558 #[inline]
559 fn hash<H: Hasher>(&self, state: &mut H) {
560 self.as_str().hash(state);
564 }
565}
566
567impl PartialEq<str> for SmartString {
568 #[inline]
569 fn eq(&self, other: &str) -> bool {
570 self.as_str() == other
571 }
572}
573
574impl PartialEq<&str> for SmartString {
575 #[inline]
576 fn eq(&self, other: &&str) -> bool {
577 self.as_str() == *other
578 }
579}
580
581impl PartialEq<String> for SmartString {
582 #[inline]
583 fn eq(&self, other: &String) -> bool {
584 self.as_str() == other
585 }
586}
587
588impl PartialEq<SmartString> for str {
589 #[inline]
590 fn eq(&self, other: &SmartString) -> bool {
591 self == other.as_str()
592 }
593}
594
595impl PartialEq<SmartString> for &str {
596 #[inline]
597 fn eq(&self, other: &SmartString) -> bool {
598 *self == other.as_str()
599 }
600}
601
602impl From<SmartString> for String {
603 #[inline]
604 fn from(s: SmartString) -> Self {
605 s.into_string()
606 }
607}
608
609impl PartialEq<SmartString> for String {
610 #[inline]
611 fn eq(&self, other: &SmartString) -> bool {
612 self.as_str() == other.as_str()
613 }
614}
615
616impl std::iter::FromIterator<char> for SmartString {
617 fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
618 let s: String = iter.into_iter().collect();
619 SmartString::from_string(s)
620 }
621}
622
623impl fmt::Write for SmartString {
624 #[inline]
625 fn write_str(&mut self, s: &str) -> fmt::Result {
626 self.push_str(s);
627 Ok(())
628 }
629
630 #[inline]
631 fn write_char(&mut self, c: char) -> fmt::Result {
632 self.push(c);
633 Ok(())
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640 use std::collections::HashMap;
641 use std::mem::size_of;
642
643 #[test]
644 fn test_size() {
645 assert_eq!(size_of::<SmartString>(), 16);
646 assert_eq!(size_of::<Option<SmartString>>(), 16);
647 }
648
649 #[test]
650 fn test_inline() {
651 let s = SmartString::new("hello");
652 assert!(s.is_inline());
653 assert!(!s.is_heap());
654 assert_eq!(s.as_str(), "hello");
655 assert_eq!(s.len(), 5);
656 }
657
658 #[test]
659 fn test_inline_max() {
660 let s = SmartString::new("123456789012345");
662 assert!(s.is_inline());
663 assert_eq!(s.len(), 15);
664 assert_eq!(s.as_str(), "123456789012345");
665 }
666
667 #[test]
668 fn test_heap() {
669 let s = SmartString::new("1234567890123456");
671 assert!(s.is_heap());
672 assert!(!s.is_inline());
673 assert_eq!(s.len(), 16);
674 assert_eq!(s.as_str(), "1234567890123456");
675 }
676
677 #[test]
678 fn test_clone_inline() {
679 let s1 = SmartString::new("hello");
680 let s2 = s1.clone();
681 assert_eq!(s1.as_str(), s2.as_str());
682 assert!(s1.is_inline());
683 assert!(s2.is_inline());
684 }
685
686 #[test]
687 fn test_clone_heap() {
688 let s1 = SmartString::new("this is a longer string that goes on the heap");
689 let s2 = s1.clone();
690 assert_eq!(s1.as_str(), s2.as_str());
691 assert!(s1.is_heap());
692 assert!(s2.is_heap());
693 }
694
695 #[test]
696 fn test_push() {
697 let mut s = SmartString::new("hello");
698 s.push(' ');
699 s.push('w');
700 assert_eq!(s.as_str(), "hello w");
701 assert!(s.is_inline());
702 }
703
704 #[test]
705 fn test_push_str() {
706 let mut s = SmartString::new("hello");
707 s.push_str(" world");
708 assert_eq!(s.as_str(), "hello world");
709 assert!(s.is_inline());
710 }
711
712 #[test]
713 fn test_push_str_to_heap() {
714 let mut s = SmartString::new("hello");
715 s.push_str(" world, this is a long string");
716 assert_eq!(s.as_str(), "hello world, this is a long string");
717 assert!(s.is_heap());
718 }
719
720 #[test]
721 fn test_from_string() {
722 let s = SmartString::from_string("hello".to_string());
723 assert!(s.is_inline());
724 assert_eq!(s.as_str(), "hello");
725
726 let s = SmartString::from_string("this is a longer string".to_string());
727 assert!(s.is_heap());
728 assert_eq!(s.as_str(), "this is a longer string");
729 }
730
731 #[test]
732 fn test_into_string() {
733 let s = SmartString::new("hello");
734 let string: String = s.into_string();
735 assert_eq!(string, "hello");
736
737 let s = SmartString::new("this is a longer string");
738 let string: String = s.into_string();
739 assert_eq!(string, "this is a longer string");
740 }
741
742 #[test]
743 fn test_eq() {
744 let s1 = SmartString::new("hello");
745 let s2 = SmartString::new("hello");
746 let s3 = SmartString::new("world");
747 assert_eq!(s1, s2);
748 assert_ne!(s1, s3);
749 }
750
751 #[test]
752 fn test_ord() {
753 let s1 = SmartString::new("apple");
754 let s2 = SmartString::new("banana");
755 assert!(s1 < s2);
756 }
757
758 #[test]
759 fn test_hash() {
760 let mut map = HashMap::new();
761 map.insert(SmartString::new("key"), "value");
762 assert_eq!(map.get(&SmartString::new("key")), Some(&"value"));
763 }
764
765 #[test]
766 fn test_borrow_lookup() {
767 use std::collections::HashMap;
768 let mut map: HashMap<SmartString, i32> = HashMap::new();
769 map.insert(SmartString::new("hello"), 42);
770
771 assert_eq!(map.get("hello"), Some(&42));
773 }
774
775 #[test]
776 fn test_concat() {
777 let s = SmartString::concat("hello", " world");
778 assert_eq!(s.as_str(), "hello world");
779 assert!(s.is_inline());
780
781 let s = SmartString::concat("hello", " world, this is a very long string");
782 assert_eq!(s.as_str(), "hello world, this is a very long string");
783 assert!(s.is_heap());
784 }
785
786 #[test]
787 fn test_empty() {
788 let s = SmartString::new("");
789 assert!(s.is_empty());
790 assert!(s.is_inline());
791 assert_eq!(s.len(), 0);
792 }
793
794 #[test]
795 fn test_unicode() {
796 let s = SmartString::new("こんにちは"); assert!(s.is_inline());
798 assert_eq!(s.as_str(), "こんにちは");
799
800 let s = SmartString::new("こんにちは!"); assert!(s.is_heap());
802 assert_eq!(s.as_str(), "こんにちは!");
803 }
804
805 #[test]
806 fn test_case_conversion() {
807 let s = SmartString::new("Hello World");
808 assert_eq!(s.to_lowercase().as_str(), "hello world");
809 assert_eq!(s.to_uppercase().as_str(), "HELLO WORLD");
810 }
811
812 #[test]
813 fn test_ascii_case() {
814 let mut s = SmartString::new("Hello");
815 s.make_ascii_uppercase();
816 assert_eq!(s.as_str(), "HELLO");
817
818 s.make_ascii_lowercase();
819 assert_eq!(s.as_str(), "hello");
820 }
821
822 #[test]
823 fn test_default() {
824 let s = SmartString::default();
825 assert!(s.is_empty());
826 assert!(s.is_inline());
827 }
828
829 #[test]
830 fn test_from_arc_str() {
831 let arc: Arc<str> = Arc::from("hello");
832 let s = SmartString::from(arc);
833 assert_eq!(s.as_str(), "hello");
834 }
835
836 #[test]
837 fn test_build_inline() {
838 let s = unsafe {
840 SmartString::build_inline(5, |buf| {
841 buf.copy_from_slice(b"hello");
842 })
843 };
844 assert!(s.is_some());
845 assert_eq!(s.unwrap().as_str(), "hello");
846
847 let s = unsafe {
849 SmartString::build_inline(20, |_| {
850 })
852 };
853 assert!(s.is_none());
854 }
855}