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(feature = "napi")]
606mod napi_impl {
607 use napi::{
608 bindgen_prelude::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue},
609 sys::{napi_env, napi_value},
610 };
611
612 use super::*;
613
614 impl TypeName for RcStr {
615 fn type_name() -> &'static str {
616 String::type_name()
617 }
618
619 fn value_type() -> napi::ValueType {
620 String::value_type()
621 }
622 }
623
624 impl ToNapiValue for RcStr {
625 unsafe fn to_napi_value(env: napi_env, val: Self) -> napi::Result<napi_value> {
626 unsafe { ToNapiValue::to_napi_value(env, val.as_str()) }
627 }
628 }
629
630 impl FromNapiValue for RcStr {
631 unsafe fn from_napi_value(env: napi_env, napi_val: napi_value) -> napi::Result<Self> {
632 Ok(RcStr::from(unsafe {
633 String::from_napi_value(env, napi_val)
634 }?))
635 }
636 }
637
638 impl ValidateNapiValue for RcStr {
639 unsafe fn validate(env: napi_env, napi_val: napi_value) -> napi::Result<napi_value> {
640 unsafe { String::validate(env, napi_val) }
641 }
642 }
643}
644
645pub struct RcStrInterning {
651 set: rustc_hash::FxHashSet<RcStr>,
652}
653
654impl Default for RcStrInterning {
655 fn default() -> Self {
656 Self::new()
657 }
658}
659
660impl RcStrInterning {
661 pub fn new() -> Self {
663 Self {
664 set: rustc_hash::FxHashSet::default(),
665 }
666 }
667
668 pub fn intern(&mut self, s: &str) -> RcStr {
674 if is_atom_inlineable(s) {
675 return RcStr::from(s);
677 }
678 if let Some(existing) = self.set.get(s) {
679 return existing.clone();
680 }
681 let rc = RcStr::from(s);
682 self.set.insert(rc.clone());
683 rc
684 }
685
686 fn intern_owned(&mut self, s: String) -> RcStr {
689 if is_atom_inlineable(&s) {
690 return RcStr::from(s);
691 }
692 if let Some(existing) = self.set.get(s.as_str()) {
693 return existing.clone();
694 }
695 let rc = RcStr::from(s);
696 self.set.insert(rc.clone());
697 rc
698 }
699
700 pub fn intern_cow(&mut self, s: std::borrow::Cow<'_, str>) -> RcStr {
703 match s {
704 std::borrow::Cow::Borrowed(s) => self.intern(s),
705 std::borrow::Cow::Owned(s) => self.intern_owned(s),
706 }
707 }
708
709 pub fn intern_display(&mut self, v: &impl std::fmt::Display) -> RcStr {
711 self.intern_owned(v.to_string())
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use std::mem::ManuallyDrop;
718
719 use super::*;
720
721 #[test]
722 fn test_refcount() {
723 fn refcount(str: &RcStr) -> usize {
724 assert!(str.tag() == DYNAMIC_TAG);
725 let arc = ManuallyDrop::new(unsafe { dynamic::restore_arc(str.unsafe_data) });
726 triomphe::Arc::count(&arc)
727 }
728
729 let str = RcStr::from("this is a long string that won't be inlined");
730
731 assert_eq!(refcount(&str), 1);
732 assert_eq!(refcount(&str), 1); let cloned_str = str.clone();
735 assert_eq!(refcount(&str), 2);
736
737 drop(cloned_str);
738 assert_eq!(refcount(&str), 1);
739
740 let _ = str.clone().into_owned();
741 assert_eq!(refcount(&str), 1);
742 }
743
744 #[test]
745 fn test_rcstr() {
746 assert_eq!(rcstr!(""), RcStr::default());
748 assert_eq!(rcstr!(""), RcStr::from(""));
749 assert_eq!(rcstr!("a"), RcStr::from("a"));
750 assert_eq!(rcstr!("ab"), RcStr::from("ab"));
751 assert_eq!(rcstr!("abc"), RcStr::from("abc"));
752 assert_eq!(rcstr!("abcd"), RcStr::from("abcd"));
753 assert_eq!(rcstr!("abcde"), RcStr::from("abcde"));
754 assert_eq!(rcstr!("abcdef"), RcStr::from("abcdef"));
755 assert_eq!(rcstr!("abcdefg"), RcStr::from("abcdefg"));
756 assert_eq!(rcstr!("abcdefgh"), RcStr::from("abcdefgh"));
757 assert_eq!(rcstr!("abcdefghi"), RcStr::from("abcdefghi"));
758 }
759
760 #[test]
761 fn test_static_atom() {
762 const LONG: &str = "a very long string that lives forever";
763 let leaked = rcstr!(LONG);
764 let not_leaked = RcStr::from(LONG);
765 assert_ne!(leaked.tag(), not_leaked.tag());
766 assert_eq!(leaked, not_leaked);
767 }
768
769 #[test]
770 fn test_inline_atom() {
771 const STR: RcStr = {
773 let inline = inline_atom("hello");
774 if inline.is_some() {
775 inline.unwrap()
776 } else {
777 unreachable!();
778 }
779 };
780 assert_eq!(STR, RcStr::from("hello"));
781
782 let too_long = "x".repeat(MAX_INLINE_LEN + 1);
784 assert!(inline_atom(&too_long).is_none());
785 }
786
787 #[test]
792 #[cfg(not(feature = "atom_size_128"))]
793 fn max_inline_len_is_uniform_across_targets() {
794 assert_eq!(
795 MAX_INLINE_LEN, 7,
796 "MAX_INLINE_LEN must be 7 on every target, including 32-bit/wasm"
797 );
798 assert_eq!(size_of::<crate::tagged_value::TaggedValue>(), 8);
799 assert_eq!(size_of::<Option<RcStr>>(), size_of::<RcStr>());
801 }
802
803 #[test]
808 fn rcstr_macro_is_const_on_every_target() {
809 const SHORT: RcStr = rcstr!("abc");
811 const LONG: RcStr = rcstr!("a string that is definitely not inline");
814
815 assert_eq!(SHORT, RcStr::from("abc"));
816 assert_eq!(LONG, RcStr::from("a string that is definitely not inline"));
817 assert_eq!(SHORT.tag(), INLINE_TAG);
818 assert_eq!(LONG.tag(), STATIC_TAG);
819 }
820
821 #[test]
825 fn round_trip_across_the_inline_boundary() {
826 for len in 0..=9usize {
827 let s = "abcdefghi"[..len].to_string();
828 let r = RcStr::from(s.as_str());
829 assert_eq!(r.as_str(), s, "round trip failed at len {len}");
830 assert_eq!(r.len(), len);
831
832 let expected_inline = len <= MAX_INLINE_LEN;
833 assert_eq!(
834 r.tag() == INLINE_TAG,
835 expected_inline,
836 "len {len} should {} be inline (MAX_INLINE_LEN = {MAX_INLINE_LEN})",
837 if expected_inline { "" } else { "not" }
838 );
839 }
840 }
841
842 #[test]
843 fn test_hash_matches_str() {
844 use std::hash::{Hash, Hasher};
845
846 use rustc_hash::FxHasher;
847
848 fn fxhash<T: Hash>(value: T) -> u64 {
849 let mut hasher = FxHasher::default();
850 value.hash(&mut hasher);
851 hasher.finish()
852 }
853
854 let test_strings = [
856 "",
857 "a",
858 "ab",
859 "abc",
860 "abcdef", "abcdefg", "abcdefgh",
863 "a very long string that exceeds sixteen bytes",
864 ];
865
866 for s in test_strings {
868 let rcstr = RcStr::from(s);
869 assert_eq!(
870 fxhash(&rcstr),
871 fxhash(s),
872 "Hash mismatch for string of length {}: {:?}",
873 s.len(),
874 s
875 );
876 }
877
878 for s1 in test_strings {
880 for s2 in test_strings {
881 let rcstr1 = RcStr::from(s1);
882 let rcstr2 = RcStr::from(s2);
883 assert_eq!(
884 fxhash((&rcstr1, &rcstr2)),
885 fxhash((s1, s2)),
886 "Tuple hash mismatch for ({:?}, {:?})",
887 s1,
888 s2
889 );
890 }
891 }
892 }
893
894 #[test]
895 #[cfg_attr(target_family = "wasm", ignore = "no static RcStr registry on wasm")]
898 fn test_bincode_roundtrip() {
899 use turbo_bincode::{turbo_bincode_decode, turbo_bincode_encode};
900
901 let short = RcStr::from("hi");
903 let encoded = turbo_bincode_encode(&short).unwrap();
904 let decoded: RcStr = turbo_bincode_decode(&encoded).unwrap();
905 assert_eq!(decoded, short);
906 assert_eq!(decoded.tag(), INLINE_TAG);
907
908 let long = RcStr::from("bincode_roundtrip: no matching rcstr constant");
910 let encoded = turbo_bincode_encode(&long).unwrap();
911 let decoded: RcStr = turbo_bincode_decode(&encoded).unwrap();
912 assert_eq!(decoded, long);
913 assert_eq!(decoded.tag(), DYNAMIC_TAG);
914
915 const STATIC_STR: &str = "bincode_roundtrip: a static constant for testing";
917 let _register = rcstr!(STATIC_STR);
918 let original = RcStr::from(STATIC_STR); let encoded = turbo_bincode_encode(&original).unwrap();
920 let decoded: RcStr = turbo_bincode_decode(&encoded).unwrap();
921 assert_eq!(decoded.as_str(), STATIC_STR);
922 assert_eq!(decoded.tag(), STATIC_TAG);
924 }
925
926 #[test]
927 fn test_interning() {
928 let mut interner = RcStrInterning::new();
929
930 let a = interner.intern("hi");
932 let b = interner.intern("hi");
933 assert_eq!(a, b);
934
935 let long = "this is a long string that exceeds inline threshold";
937 let c = interner.intern(long);
938 let d = interner.intern(long);
939 assert_eq!(c, d);
940 assert!(std::ptr::eq(c.as_str().as_ptr(), d.as_str().as_ptr()));
941
942 let e = interner.intern_cow(std::borrow::Cow::Borrowed(long));
944 assert_eq!(e, c);
945 assert!(std::ptr::eq(e.as_str().as_ptr(), c.as_str().as_ptr()));
946
947 let f = interner.intern_cow(std::borrow::Cow::Owned(long.to_string()));
949 assert_eq!(f, c);
950 assert!(std::ptr::eq(f.as_str().as_ptr(), c.as_str().as_ptr()));
951
952 let long2 = "another long string that exceeds the inline threshold here";
954 let g = interner.intern_display(&long2);
955 let h = interner.intern_display(&long2);
956 assert_eq!(g, h);
957 assert!(std::ptr::eq(g.as_str().as_ptr(), h.as_str().as_ptr()));
958 }
959}