1extern crate self as turbo_rcstr;
4
5use std::{
6 borrow::{Borrow, Cow},
7 collections::HashMap,
8 ffi::OsStr,
9 fmt::{Debug, Display},
10 hash::{Hash, Hasher},
11 mem::{ManuallyDrop, forget},
12 num::NonZeroU8,
13 ops::Deref,
14 path::{Path, PathBuf},
15 sync::LazyLock,
16};
17
18use bincode::{
19 Decode, Encode,
20 de::{Decoder, read::Reader},
21 enc::Encoder,
22 error::{DecodeError, EncodeError},
23 impl_borrow_decode,
24};
25use bytes_str::BytesStr;
26use debug_unreachable::debug_unreachable;
27use rustc_hash::FxBuildHasher;
28#[cfg(not(target_family = "wasm"))]
29use scattered_collect::slice::ScatteredSlice;
30use serde::{Deserialize, Deserializer, Serialize, Serializer};
31use shrink_to_fit::ShrinkToFit;
32use smallvec::SmallVec;
33use triomphe::Arc;
34use turbo_tasks_hash::{DeterministicHash, DeterministicHasher};
35
36use crate::{
37 dynamic::{
38 DynamicPrehashedString, deref_dynamic, deref_static, hash_bytes, new_atom,
39 new_atom_from_prehashed, new_static_atom,
40 },
41 tagged_value::{MAX_INLINE_LEN, TaggedValue},
42};
43
44mod dynamic;
45mod tagged_value;
46
47pub struct RcStr {
91 unsafe_data: TaggedValue,
92}
93
94const _: () = {
95 assert!(std::mem::size_of::<RcStr>() == std::mem::size_of::<Option<RcStr>>());
97};
98
99unsafe impl Send for RcStr {}
100unsafe impl Sync for RcStr {}
101
102const DYNAMIC_TAG: u8 = 0b_10;
104const STATIC_TAG: u8 = 0b_00;
106const INLINE_TAG: u8 = 0b_01; const INLINE_TAG_INIT: NonZeroU8 = NonZeroU8::new(INLINE_TAG).unwrap();
109const TAG_MASK: u8 = 0b_11;
110const LEN_OFFSET: usize = 4;
112const LEN_MASK: u8 = 0xf0;
113
114impl RcStr {
115 #[inline(always)]
116 fn tag(&self) -> u8 {
117 self.unsafe_data.tag_byte() & TAG_MASK
118 }
119
120 #[inline(never)]
121 pub fn as_str(&self) -> &str {
122 match self.tag() {
123 STATIC_TAG => unsafe { deref_static(self.unsafe_data).value },
124 DYNAMIC_TAG => unsafe { &deref_dynamic(self.unsafe_data).value },
125 INLINE_TAG => self.inline_as_str(),
126 _ => unsafe { debug_unreachable!() },
127 }
128 }
129
130 fn inline_as_str(&self) -> &str {
131 debug_assert!(self.tag() == INLINE_TAG);
132 let len = (self.unsafe_data.tag_byte() & LEN_MASK) >> LEN_OFFSET;
133 let src = self.unsafe_data.data();
134 unsafe { std::str::from_utf8_unchecked(&src[..(len as usize)]) }
135 }
136
137 pub fn into_owned(self) -> String {
145 match self.tag() {
146 DYNAMIC_TAG => {
147 let arc = unsafe { dynamic::restore_arc(ManuallyDrop::new(self).unsafe_data) };
149 match Arc::try_unwrap(arc) {
150 Ok(v) => String::from(v.value),
152 Err(arc) => arc.value.to_string(),
153 }
154 }
155 INLINE_TAG => self.inline_as_str().to_string(),
156 STATIC_TAG => unsafe { deref_static(self.unsafe_data).value.to_string() },
157 _ => unsafe { debug_unreachable!() },
158 }
159 }
160
161 pub fn map(self, f: impl FnOnce(String) -> String) -> Self {
162 RcStr::from(Cow::Owned(f(self.into_owned())))
163 }
164
165 fn from_deserialized(s: &str) -> Self {
172 if !is_atom_inlineable(s) {
173 let hash = hash_bytes(s.as_bytes());
174 if let Some(entries) = STATIC_TABLE.get(&hash)
176 && let Some(static_phs) = entries.iter().find(|phs| phs.value == s)
177 {
178 new_static_atom(static_phs)
179 } else {
180 new_atom_from_prehashed(DynamicPrehashedString {
181 hash,
182 value: s.into(),
183 })
184 }
185 } else {
186 inline_atom(s).unwrap()
187 }
188 }
189}
190
191impl DeterministicHash for RcStr {
192 fn deterministic_hash<H: DeterministicHasher>(&self, state: &mut H) {
193 state.write_usize(self.len());
194 state.write_bytes(self.as_bytes());
195 }
196}
197
198impl Deref for RcStr {
199 type Target = str;
200
201 fn deref(&self) -> &Self::Target {
202 self.as_str()
203 }
204}
205
206impl Borrow<str> for RcStr {
207 fn borrow(&self) -> &str {
208 self.as_str()
209 }
210}
211
212impl AsRef<str> for RcStr {
213 fn as_ref(&self) -> &str {
214 self.as_str()
215 }
216}
217
218impl From<BytesStr> for RcStr {
219 fn from(s: BytesStr) -> Self {
220 let bytes: Vec<u8> = s.into_bytes().into();
221 RcStr::from(unsafe {
222 String::from_utf8_unchecked(bytes)
224 })
225 }
226}
227
228impl From<Arc<String>> for RcStr {
229 fn from(s: Arc<String>) -> Self {
230 match Arc::try_unwrap(s) {
231 Ok(v) => new_atom(Cow::Owned(v)),
232 Err(arc) => new_atom(Cow::Borrowed(&**arc)),
233 }
234 }
235}
236
237impl From<String> for RcStr {
238 fn from(s: String) -> Self {
239 new_atom(Cow::Owned(s))
240 }
241}
242
243impl From<&'_ str> for RcStr {
244 fn from(s: &str) -> Self {
245 new_atom(Cow::Borrowed(s))
246 }
247}
248
249impl From<Cow<'_, str>> for RcStr {
250 fn from(s: Cow<str>) -> Self {
251 new_atom(s)
252 }
253}
254
255impl AsRef<Path> for RcStr {
257 fn as_ref(&self) -> &Path {
258 self.as_str().as_ref()
259 }
260}
261
262impl AsRef<OsStr> for RcStr {
264 fn as_ref(&self) -> &OsStr {
265 self.as_str().as_ref()
266 }
267}
268
269impl AsRef<[u8]> for RcStr {
271 fn as_ref(&self) -> &[u8] {
272 self.as_str().as_ref()
273 }
274}
275
276impl From<RcStr> for BytesStr {
277 fn from(value: RcStr) -> Self {
278 Self::from_str_slice(value.as_str())
279 }
280}
281
282impl PartialEq<str> for RcStr {
283 fn eq(&self, other: &str) -> bool {
284 self.as_str() == other
285 }
286}
287
288impl PartialEq<&'_ str> for RcStr {
289 fn eq(&self, other: &&str) -> bool {
290 self.as_str() == *other
291 }
292}
293
294impl PartialEq<String> for RcStr {
295 fn eq(&self, other: &String) -> bool {
296 self.as_str() == other.as_str()
297 }
298}
299
300impl Debug for RcStr {
301 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302 Debug::fmt(&self.as_str(), f)
303 }
304}
305
306impl Display for RcStr {
307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308 Display::fmt(&self.as_str(), f)
309 }
310}
311
312impl From<RcStr> for String {
313 fn from(s: RcStr) -> Self {
314 s.into_owned()
315 }
316}
317
318impl From<RcStr> for PathBuf {
319 fn from(s: RcStr) -> Self {
320 String::from(s).into()
321 }
322}
323
324impl Clone for RcStr {
325 #[inline(always)]
326 fn clone(&self) -> Self {
327 if self.tag() == DYNAMIC_TAG {
330 unsafe {
331 let arc = dynamic::restore_arc(self.unsafe_data);
332 forget(arc.clone());
333 forget(arc);
334 }
335 }
336
337 RcStr {
338 unsafe_data: self.unsafe_data,
339 }
340 }
341}
342
343impl Default for RcStr {
344 fn default() -> Self {
345 rcstr!("")
346 }
347}
348
349impl PartialEq for RcStr {
350 fn eq(&self, other: &Self) -> bool {
351 if self.unsafe_data == other.unsafe_data {
354 return true;
355 }
356 if self.tag() == INLINE_TAG || other.tag() == INLINE_TAG {
360 return false;
361 }
362
363 let (l_hash, l_str) = unsafe { heap_hash_and_str(self) };
365 let (r_hash, r_str) = unsafe { heap_hash_and_str(other) };
366 l_hash == r_hash && l_str == r_str
367 }
368}
369
370#[inline]
372unsafe fn heap_hash_and_str(s: &RcStr) -> (u64, &str) {
373 match s.tag() {
374 STATIC_TAG => {
375 let p = unsafe { deref_static(s.unsafe_data) };
376 (p.hash, p.value)
377 }
378 DYNAMIC_TAG => {
379 let p = unsafe { deref_dynamic(s.unsafe_data) };
380 (p.hash, &p.value)
381 }
382 _ => unsafe { debug_unreachable!() },
383 }
384}
385
386impl Eq for RcStr {}
387
388impl PartialOrd for RcStr {
389 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
390 Some(self.cmp(other))
391 }
392}
393
394impl Ord for RcStr {
395 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
396 self.as_str().cmp(other.as_str())
397 }
398}
399
400impl Hash for RcStr {
401 fn hash<H: Hasher>(&self, state: &mut H) {
402 match self.tag() {
403 STATIC_TAG => {
404 state.write_u64(unsafe { deref_static(self.unsafe_data).hash });
405 state.write_u8(0xff); }
407 DYNAMIC_TAG => {
408 state.write_u64(unsafe { deref_dynamic(self.unsafe_data).hash });
409 state.write_u8(0xff); }
411 INLINE_TAG => {
412 self.inline_as_str().hash(state);
413 }
414 _ => unsafe { debug_unreachable!() },
415 }
416 }
417}
418
419impl Serialize for RcStr {
420 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
421 serializer.serialize_str(self.as_str())
422 }
423}
424
425impl<'de> Deserialize<'de> for RcStr {
426 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
427 struct RcStrVisitor;
428
429 impl serde::de::Visitor<'_> for RcStrVisitor {
430 type Value = RcStr;
431
432 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
433 f.write_str("a string")
434 }
435
436 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<RcStr, E> {
437 Ok(RcStr::from_deserialized(v))
438 }
439
440 fn visit_string<E: serde::de::Error>(self, v: String) -> Result<RcStr, E> {
441 Ok(RcStr::from_deserialized(&v))
442 }
443 }
444
445 deserializer.deserialize_str(RcStrVisitor)
446 }
447}
448
449impl Encode for RcStr {
450 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
451 self.as_str().encode(encoder)
452 }
453}
454
455impl<Context> Decode<Context> for RcStr {
456 fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
457 let len = u64::decode(decoder)?;
459 let len: usize = len
460 .try_into()
461 .map_err(|_| DecodeError::OutsideUsizeRange(len))?;
462
463 if unty::type_equal::<D::R, turbo_bincode::TurboBincodeReader>() {
464 let bytes = decoder
467 .reader()
468 .peek_read(len)
469 .ok_or(DecodeError::UnexpectedEnd { additional: len })?;
470 let s = core::str::from_utf8(bytes).map_err(|inner| DecodeError::Utf8 { inner })?;
471 let rcstr = RcStr::from_deserialized(s);
472 decoder.reader().consume(len);
473 Ok(rcstr)
474 } else {
475 unreachable!(
476 "RcStr::decode expected TurboBincodeReader, but was called with a {} reader",
477 std::any::type_name::<D::R>(),
478 )
479 }
480 }
481}
482
483impl_borrow_decode!(RcStr);
484
485impl Drop for RcStr {
486 fn drop(&mut self) {
487 match self.tag() {
488 DYNAMIC_TAG => unsafe { drop(dynamic::restore_arc(self.unsafe_data)) },
489 INLINE_TAG | STATIC_TAG => {
490 }
492 _ => unsafe { debug_unreachable!() },
493 }
494 }
495}
496
497#[doc(hidden)]
499pub const fn inline_atom(s: &str) -> Option<RcStr> {
500 dynamic::inline_atom(s)
501}
502
503#[doc(hidden)]
505pub const fn is_atom_inlineable(s: &str) -> bool {
506 s.len() <= MAX_INLINE_LEN
507}
508
509#[doc(hidden)]
510#[inline(always)]
511pub const fn from_static(s: &'static StaticPrehashedString) -> RcStr {
512 dynamic::new_static_atom(s)
513}
514#[doc(hidden)]
515pub use dynamic::StaticPrehashedString;
516
517#[doc(hidden)]
518pub const fn make_const_prehashed_string(text: &'static str) -> StaticPrehashedString {
519 StaticPrehashedString {
520 value: text,
521 hash: hash_bytes(text.as_bytes()),
522 }
523}
524
525#[cfg(not(target_family = "wasm"))]
528#[doc(hidden)]
529pub use scattered_collect;
530
531#[doc(hidden)]
533pub struct StaticRcStr(pub &'static StaticPrehashedString);
534
535#[cfg(not(target_family = "wasm"))]
542#[doc(hidden)]
543#[scattered_collect::gather]
544pub static STATIC_RCSTRS: ScatteredSlice<StaticRcStr>;
545#[cfg(target_family = "wasm")]
547const STATIC_RCSTRS: [StaticRcStr; 0] = [];
548
549#[doc(hidden)]
551#[macro_export]
552macro_rules! __rcstr_static_submit {
553 ($value:expr) => {
554 #[cfg(not(target_family = "wasm"))]
555 $crate::scattered_collect::declarative::scatter! {
556 #[scatter($crate::STATIC_RCSTRS)]
557 const _: $crate::StaticRcStr = $value;
558 }
559 };
560}
561
562static STATIC_TABLE: LazyLock<
569 HashMap<u64, SmallVec<[&'static StaticPrehashedString; 1]>, FxBuildHasher>,
570> = LazyLock::new(|| {
571 let mut map: HashMap<u64, SmallVec<[&'static StaticPrehashedString; 1]>, FxBuildHasher> =
572 HashMap::with_hasher(FxBuildHasher);
573 for &StaticRcStr(phs) in STATIC_RCSTRS.iter() {
574 if phs.value.len() <= MAX_INLINE_LEN {
575 continue;
581 }
582 let entries = map.entry(phs.hash).or_default();
583 if !entries.iter().any(|e| e.value == phs.value) {
587 entries.push(phs);
588 }
589 }
590 map.shrink_to_fit(); map
592});
593
594pub use turbo_rcstr_macros::rcstr;
598
599impl ShrinkToFit for RcStr {
601 #[inline(always)]
602 fn shrink_to_fit(&mut self) {}
603}
604
605#[cfg(all(feature = "napi", target_family = "wasm"))]
606compile_error!("The napi feature cannot be enabled for wasm targets");
607
608#[cfg(all(feature = "napi", not(target_family = "wasm")))]
609mod napi_impl {
610 use napi::{
611 bindgen_prelude::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue},
612 sys::{napi_env, napi_value},
613 };
614
615 use super::*;
616
617 impl TypeName for RcStr {
618 fn type_name() -> &'static str {
619 String::type_name()
620 }
621
622 fn value_type() -> napi::ValueType {
623 String::value_type()
624 }
625 }
626
627 impl ToNapiValue for RcStr {
628 unsafe fn to_napi_value(env: napi_env, val: Self) -> napi::Result<napi_value> {
629 unsafe { ToNapiValue::to_napi_value(env, val.as_str()) }
630 }
631 }
632
633 impl FromNapiValue for RcStr {
634 unsafe fn from_napi_value(env: napi_env, napi_val: napi_value) -> napi::Result<Self> {
635 Ok(RcStr::from(unsafe {
636 String::from_napi_value(env, napi_val)
637 }?))
638 }
639 }
640
641 impl ValidateNapiValue for RcStr {
642 unsafe fn validate(env: napi_env, napi_val: napi_value) -> napi::Result<napi_value> {
643 unsafe { String::validate(env, napi_val) }
644 }
645 }
646}
647
648pub struct RcStrInterning {
654 set: rustc_hash::FxHashSet<RcStr>,
655}
656
657impl Default for RcStrInterning {
658 fn default() -> Self {
659 Self::new()
660 }
661}
662
663impl RcStrInterning {
664 pub fn new() -> Self {
666 Self {
667 set: rustc_hash::FxHashSet::default(),
668 }
669 }
670
671 pub fn intern(&mut self, s: &str) -> RcStr {
677 if is_atom_inlineable(s) {
678 return RcStr::from(s);
680 }
681 if let Some(existing) = self.set.get(s) {
682 return existing.clone();
683 }
684 let rc = RcStr::from(s);
685 self.set.insert(rc.clone());
686 rc
687 }
688
689 fn intern_owned(&mut self, s: String) -> RcStr {
692 if is_atom_inlineable(&s) {
693 return RcStr::from(s);
694 }
695 if let Some(existing) = self.set.get(s.as_str()) {
696 return existing.clone();
697 }
698 let rc = RcStr::from(s);
699 self.set.insert(rc.clone());
700 rc
701 }
702
703 pub fn intern_cow(&mut self, s: std::borrow::Cow<'_, str>) -> RcStr {
706 match s {
707 std::borrow::Cow::Borrowed(s) => self.intern(s),
708 std::borrow::Cow::Owned(s) => self.intern_owned(s),
709 }
710 }
711
712 pub fn intern_display(&mut self, v: &impl std::fmt::Display) -> RcStr {
714 self.intern_owned(v.to_string())
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use std::mem::ManuallyDrop;
721
722 use super::*;
723
724 #[test]
725 fn test_refcount() {
726 fn refcount(str: &RcStr) -> usize {
727 assert!(str.tag() == DYNAMIC_TAG);
728 let arc = ManuallyDrop::new(unsafe { dynamic::restore_arc(str.unsafe_data) });
729 triomphe::Arc::count(&arc)
730 }
731
732 let str = RcStr::from("this is a long string that won't be inlined");
733
734 assert_eq!(refcount(&str), 1);
735 assert_eq!(refcount(&str), 1); let cloned_str = str.clone();
738 assert_eq!(refcount(&str), 2);
739
740 drop(cloned_str);
741 assert_eq!(refcount(&str), 1);
742
743 let _ = str.clone().into_owned();
744 assert_eq!(refcount(&str), 1);
745 }
746
747 #[test]
748 fn test_rcstr() {
749 assert_eq!(rcstr!(""), RcStr::default());
751 assert_eq!(rcstr!(""), RcStr::from(""));
752 assert_eq!(rcstr!("a"), RcStr::from("a"));
753 assert_eq!(rcstr!("ab"), RcStr::from("ab"));
754 assert_eq!(rcstr!("abc"), RcStr::from("abc"));
755 assert_eq!(rcstr!("abcd"), RcStr::from("abcd"));
756 assert_eq!(rcstr!("abcde"), RcStr::from("abcde"));
757 assert_eq!(rcstr!("abcdef"), RcStr::from("abcdef"));
758 assert_eq!(rcstr!("abcdefg"), RcStr::from("abcdefg"));
759 assert_eq!(rcstr!("abcdefgh"), RcStr::from("abcdefgh"));
760 assert_eq!(rcstr!("abcdefghi"), RcStr::from("abcdefghi"));
761 }
762
763 #[test]
764 fn test_static_atom() {
765 const LONG: &str = "a very long string that lives forever";
766 let leaked = rcstr!(LONG);
767 let not_leaked = RcStr::from(LONG);
768 assert_ne!(leaked.tag(), not_leaked.tag());
769 assert_eq!(leaked, not_leaked);
770 }
771
772 #[test]
773 fn test_inline_atom() {
774 const STR: RcStr = {
776 let inline = inline_atom("hello");
777 if inline.is_some() {
778 inline.unwrap()
779 } else {
780 unreachable!();
781 }
782 };
783 assert_eq!(STR, RcStr::from("hello"));
784 }
785
786 #[test]
787 fn test_hash_matches_str() {
788 use std::hash::{Hash, Hasher};
789
790 use rustc_hash::FxHasher;
791
792 fn fxhash<T: Hash>(value: T) -> u64 {
793 let mut hasher = FxHasher::default();
794 value.hash(&mut hasher);
795 hasher.finish()
796 }
797
798 let test_strings = [
800 "",
801 "a",
802 "ab",
803 "abc",
804 "abcdef", "abcdefg", "abcdefgh",
807 "a very long string that exceeds sixteen bytes",
808 ];
809
810 for s in test_strings {
812 let rcstr = RcStr::from(s);
813 assert_eq!(
814 fxhash(&rcstr),
815 fxhash(s),
816 "Hash mismatch for string of length {}: {:?}",
817 s.len(),
818 s
819 );
820 }
821
822 for s1 in test_strings {
824 for s2 in test_strings {
825 let rcstr1 = RcStr::from(s1);
826 let rcstr2 = RcStr::from(s2);
827 assert_eq!(
828 fxhash((&rcstr1, &rcstr2)),
829 fxhash((s1, s2)),
830 "Tuple hash mismatch for ({:?}, {:?})",
831 s1,
832 s2
833 );
834 }
835 }
836 }
837
838 #[test]
839 fn test_bincode_roundtrip() {
840 use turbo_bincode::{turbo_bincode_decode, turbo_bincode_encode};
841
842 let short = RcStr::from("hi");
844 let encoded = turbo_bincode_encode(&short).unwrap();
845 let decoded: RcStr = turbo_bincode_decode(&encoded).unwrap();
846 assert_eq!(decoded, short);
847 assert_eq!(decoded.tag(), INLINE_TAG);
848
849 let long = RcStr::from("bincode_roundtrip: no matching rcstr constant");
851 let encoded = turbo_bincode_encode(&long).unwrap();
852 let decoded: RcStr = turbo_bincode_decode(&encoded).unwrap();
853 assert_eq!(decoded, long);
854 assert_eq!(decoded.tag(), DYNAMIC_TAG);
855
856 const STATIC_STR: &str = "bincode_roundtrip: a static constant for testing";
858 let _register = rcstr!(STATIC_STR);
859 let original = RcStr::from(STATIC_STR); let encoded = turbo_bincode_encode(&original).unwrap();
861 let decoded: RcStr = turbo_bincode_decode(&encoded).unwrap();
862 assert_eq!(decoded.as_str(), STATIC_STR);
863 assert_eq!(decoded.tag(), STATIC_TAG);
865 }
866
867 #[test]
868 fn test_interning() {
869 let mut interner = RcStrInterning::new();
870
871 let a = interner.intern("hi");
873 let b = interner.intern("hi");
874 assert_eq!(a, b);
875
876 let long = "this is a long string that exceeds inline threshold";
878 let c = interner.intern(long);
879 let d = interner.intern(long);
880 assert_eq!(c, d);
881 assert!(std::ptr::eq(c.as_str().as_ptr(), d.as_str().as_ptr()));
882
883 let e = interner.intern_cow(std::borrow::Cow::Borrowed(long));
885 assert_eq!(e, c);
886 assert!(std::ptr::eq(e.as_str().as_ptr(), c.as_str().as_ptr()));
887
888 let f = interner.intern_cow(std::borrow::Cow::Owned(long.to_string()));
890 assert_eq!(f, c);
891 assert!(std::ptr::eq(f.as_str().as_ptr(), c.as_str().as_ptr()));
892
893 let long2 = "another long string that exceeds the inline threshold here";
895 let g = interner.intern_display(&long2);
896 let h = interner.intern_display(&long2);
897 assert_eq!(g, h);
898 assert!(std::ptr::eq(g.as_str().as_ptr(), h.as_str().as_ptr()));
899 }
900}