stringtheory/
lib.rs

1//! Sharing-optimized strings and string interning utilities.
2//!
3//! `stringtheory` provides two main components: a sharing-optimized string type, `MetaString`, and string interning
4//! implementations (`FixedSizeInterner`, `GenericMapInterner`, etc). These components are meant to work in concert,
5//! allowing for using a single string type that can handle owned, shared, and interned strings, and providing a way to
6//! efficiently intern strings when possible.
7#![deny(warnings)]
8#![deny(missing_docs)]
9// The tag enum values are shifted into one byte of a `usize`, so their values are never truncated on supported
10// platforms.
11#![allow(clippy::enum_clike_unportable_variant)]
12
13use std::{
14    borrow::Borrow, fmt, hash, mem::ManuallyDrop, ops::Deref, ptr::NonNull, slice::from_raw_parts,
15    str::from_utf8_unchecked, sync::Arc,
16};
17
18use serde::{Deserialize, Serialize};
19
20mod clone;
21
22pub use self::clone::CheapMetaString;
23
24pub mod interning;
25use self::interning::{InternedString, InternedStringState, Interner};
26
27const ZERO_VALUE: usize = 0;
28const TOP_MOST_BIT: usize = usize::MAX & !(isize::MAX as usize);
29const INLINED_STR_DATA_BUF_LEN: usize = std::mem::size_of::<usize>() * 3;
30// MetaString stores its discriminant in the top byte of the third machine word, which lands at a different byte
31// offset in memory depending on endianness.
32const TOP_BYTE_INDEX: usize = if cfg!(target_endian = "little") {
33    std::mem::size_of::<usize>() - 1
34} else {
35    0
36};
37const INLINED_STR_TAG_INDEX: usize = std::mem::size_of::<usize>() * 2 + TOP_BYTE_INDEX;
38const INLINED_STR_MAX_LEN: usize = INLINED_STR_TAG_INDEX;
39const INLINED_STR_MAX_LEN_U8: u8 = INLINED_STR_MAX_LEN as u8;
40
41const UNION_TYPE_TAG_VALUE_STATIC: u8 = get_offset_tag_value(0);
42const UNION_TYPE_TAG_VALUE_INTERNED_FIXED_SIZE: u8 = get_offset_tag_value(1);
43const UNION_TYPE_TAG_VALUE_INTERNED_GENERIC_MAP: u8 = get_offset_tag_value(2);
44const UNION_TYPE_TAG_VALUE_SHARED: u8 = get_offset_tag_value(3);
45
46const fn get_offset_tag_value(tag: u8) -> u8 {
47    const UNION_TYPE_TAG_VALUE_BASE: u8 = INLINED_STR_MAX_LEN as u8 + 1;
48
49    if tag > (u8::MAX - INLINED_STR_MAX_LEN as u8) {
50        panic!("Union type tag value must fit in the discriminant byte.");
51    }
52
53    tag + UNION_TYPE_TAG_VALUE_BASE
54}
55
56const fn get_scaled_union_tag(tag: u8) -> usize {
57    // We want to shift our tag value (single byte) to make it the top most byte in a `usize`.
58    const UNION_TYPE_TAG_VALUE_SHIFT: u32 = (std::mem::size_of::<usize>() as u32 - 1) * 8;
59
60    (tag as usize) << UNION_TYPE_TAG_VALUE_SHIFT
61}
62
63// High-level invariant checks to ensure `stringtheory` isn't being used on an unsupported platform.
64#[cfg(not(target_pointer_width = "64"))]
65const _INVARIANTS_CHECK: () = {
66    compile_error!("`stringtheory` is only supported on 64-bit platforms.");
67};
68
69const fn is_tagged(cap: u8) -> bool {
70    cap & 0b10000000 != 0
71}
72
73const fn tag_cap(cap: usize) -> usize {
74    cap | TOP_MOST_BIT
75}
76
77const fn untag_cap(cap: usize) -> usize {
78    cap & !(TOP_MOST_BIT)
79}
80
81#[derive(Clone, Copy, Eq, PartialEq)]
82#[repr(usize)]
83enum Zero {
84    Zero = ZERO_VALUE,
85}
86
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88#[repr(usize)]
89enum Tag {
90    Static = get_scaled_union_tag(UNION_TYPE_TAG_VALUE_STATIC),
91    InternedFixedSize = get_scaled_union_tag(UNION_TYPE_TAG_VALUE_INTERNED_FIXED_SIZE),
92    InternedGenericMap = get_scaled_union_tag(UNION_TYPE_TAG_VALUE_INTERNED_GENERIC_MAP),
93    Shared = get_scaled_union_tag(UNION_TYPE_TAG_VALUE_SHARED),
94}
95
96#[repr(C)]
97#[derive(Clone, Copy)]
98struct EmptyUnion {
99    ptr: Zero, // Field one.
100    len: Zero, // Field two.
101    cap: Zero, // Field three.
102}
103
104impl EmptyUnion {
105    const fn new() -> Self {
106        Self {
107            ptr: Zero::Zero,
108            len: Zero::Zero,
109            cap: Zero::Zero,
110        }
111    }
112}
113
114#[repr(C)]
115#[derive(Clone, Copy)]
116struct OwnedUnion {
117    ptr: *mut u8, // Field one
118    len: usize,   // Field two.
119    cap: usize,   // Field three.
120}
121
122impl OwnedUnion {
123    #[inline]
124    fn as_str(&self) -> &str {
125        // SAFETY: We know our pointer is valid, and non-null, since it's derived from a valid `String`, and that the
126        // data it points to is valid UTF-8, again, by virtue of it being derived from a valid `String`.
127        unsafe { from_utf8_unchecked(from_raw_parts(self.ptr, self.len)) }
128    }
129
130    fn into_owned(self) -> String {
131        // SAFETY: We know our pointer is valid, and non-null, since it's derived from a valid `String`, and that the
132        // data it points to is valid UTF-8, again, by virtue of it being derived from a valid `String`.
133        unsafe { String::from_raw_parts(self.ptr, self.len, untag_cap(self.cap)) }
134    }
135}
136
137#[repr(C)]
138#[derive(Clone, Copy)]
139struct StaticUnion {
140    value: &'static str, // Fields one and two.
141    _cap: Tag,           // Field three.
142}
143
144impl StaticUnion {
145    #[inline]
146    const fn as_str(&self) -> &str {
147        self.value
148    }
149}
150
151#[repr(C)]
152struct InternedUnion {
153    state: InternedStateUnion, // Fields one and two.
154    _cap: Tag,                 // Field three.
155}
156
157impl Drop for InternedUnion {
158    fn drop(&mut self) {
159        match self._cap {
160            Tag::InternedFixedSize => {
161                let state = unsafe { ManuallyDrop::take(&mut self.state.fixed_size) };
162                drop(state);
163            }
164            Tag::InternedGenericMap => {
165                let state = unsafe { ManuallyDrop::take(&mut self.state.generic_map) };
166                drop(state);
167            }
168            _ => unreachable!(),
169        }
170    }
171}
172
173impl Clone for InternedUnion {
174    fn clone(&self) -> Self {
175        // SAFETY: We use our tag (stored in `_cap`) to determine which field to access and clone, which
176        // ensures we only access fields which are initialized.
177        let state = unsafe {
178            match self._cap {
179                Tag::InternedFixedSize => InternedStateUnion {
180                    fixed_size: self.state.fixed_size.clone(),
181                },
182                Tag::InternedGenericMap => InternedStateUnion {
183                    generic_map: self.state.generic_map.clone(),
184                },
185                _ => unreachable!(),
186            }
187        };
188
189        Self { state, _cap: self._cap }
190    }
191}
192
193#[repr(C)]
194union InternedStateUnion {
195    fixed_size: ManuallyDrop<self::interning::fixed_size::StringState>,
196    generic_map: ManuallyDrop<self::interning::map::StringState>,
197}
198
199#[repr(C)]
200struct SharedUnion {
201    ptr: NonNull<str>, // Fields one and two
202    _cap: Tag,         // Field three.
203}
204
205impl SharedUnion {
206    #[inline]
207    const fn as_str(&self) -> &str {
208        // SAFETY: The pointee is still live by virtue of being held in an `Arc`.
209        unsafe { self.ptr.as_ref() }
210    }
211}
212
213#[repr(C)]
214#[derive(Clone, Copy)]
215struct InlinedUnion {
216    // Data is arranged as contiguous string data followed by the endian-specific length/discriminant byte.
217    data: [u8; INLINED_STR_DATA_BUF_LEN], // Fields one, two, and three.
218}
219
220impl InlinedUnion {
221    #[inline]
222    fn as_str(&self) -> &str {
223        let len = self.data[INLINED_STR_MAX_LEN] as usize;
224
225        // SAFETY: We know our data is valid UTF-8 since we only ever derive inlined strings from a valid string
226        // reference.
227        unsafe { from_utf8_unchecked(&self.data[0..len]) }
228    }
229}
230
231#[repr(C)]
232#[derive(Clone, Copy)]
233struct DiscriminantUnion {
234    // Fields one, two, and three. The active discriminant byte is selected by `INLINED_STR_TAG_INDEX`.
235    data: [u8; INLINED_STR_DATA_BUF_LEN],
236}
237
238#[derive(Debug, Eq, PartialEq)]
239enum UnionType {
240    Empty,
241    Owned,
242    Static,
243    InternedFixedSize,
244    InternedGenericMap,
245    Inlined,
246    Shared,
247}
248
249impl UnionType {
250    #[inline]
251    const fn is_owned(&self) -> bool {
252        matches!(self, UnionType::Owned)
253    }
254}
255
256impl DiscriminantUnion {
257    #[inline]
258    const fn get_union_type(&self) -> UnionType {
259        // At a high level, we encode the type of the union into one byte of the struct, which overlaps with the
260        // high byte of the third machine word: the capacity of owned strings, the length of an inlined string,
261        // and the unused/tag field of other string types.
262        //
263        // Our logic is simple here:
264        //
265        // - Allocations can only ever be as large as `isize::MAX`, which means that the top-most bit of the capacity
266        //   value would never be used for a valid allocation.
267        // - In turn, the length of a string can also only ever be as large as `isize::MAX`, which means that the
268        //   top-most bit of the length byte for any string type would never be used
269        // - Inlined strings can only ever be as long as the endian-specific inline capacity, which means that the
270        //   top-most bit of the length byte for an inlined string would never be used.
271        // - Static strings and interned strings only occupy the first two fields, which means their capacity should not
272        //   be used.
273        //
274        // As such, we encode the possible string types as follows:
275        //
276        // - when all fields are zero, we have an empty string
277        // - when the tag byte has the top bit set, we have an owned string
278        // - when the tag byte does _not_ have the top bit set, and the value is less than or equal to the inline
279        //   capacity, we have an inlined string
280        // - when the tag byte does _not_ have the top bit set, and the value is greater than the inline capacity, we
281        //   interpret the specific value of the tag byte as a discriminant for the remaining string types (static,
282        //   interned, etc)
283        //
284        // The tag byte is the byte where the upper-most bits of the third machine word are stored. This overlaps the
285        // high byte of an owned string's capacity, the length byte of an inlined string, and the unused/tag field of
286        // static, interned, and shared strings. The actual byte index differs by endianness: little-endian uses the
287        // last byte of the layout, while big-endian uses the first byte of the third machine word.
288        //
289        // The little-endian layout for an inlined string and an owned string looks like this:
290        //
291        //                ~ an inlined string, "hello, world", with a length of 12 (0C) ~
292        //      ┌───────────────────────────────────────────────────────────────────────────────┐
293        //      │ 68 65 6C 6C 6F 20 77 6F    72 6C 64 21 ?? ?? ?? ??    ?? ?? ?? ?? ?? ?? ?? 0C │
294        //      └───────────────────────────────────────────────────────────────────────────────┘
295        //                                                                                    ▲
296        //                                                                                    ├──── tag byte
297        //                          ~ an owned string with a capacity of 64 ~                 ▼
298        //      ┌─────────────────────────┐┌─────────────────────────┐┌─────────────────────────┐
299        //      │ ?? ?? ?? ?? ?? ?? ?? ?? ││ ?? ?? ?? ?? ?? ?? ?? ?? ││ 40 00 00 00 00 00 00 80 │
300        //      └─────────────────────────┘└─────────────────────────┘└─────────────────────────┘
301        //                                                                                    ▲
302        //                 original capacity: 64                  (0x0000000000000040)        │
303        //                 "tagged" capacity: 9223372036854775872 (0x8000000000000040)        │
304        //                                                           ▲                        │
305        //                     (tag bit) ────────────────────────────┘                        │
306        //                                                                                    │
307        //                       inlined tag byte (0x0C)  [0 0 0 0 1 1 0 0] ◀────────────────┤
308        //                       owned tag byte (0x80)    [1 0 0 0 0 0 0 0] ◀────────────────┤
309        //                                                                                    │
310        //                       maximum little-endian inline length                           │
311        //                       23 (0x17)                [0 0 0 1 0 1 1 1] ◀────────────────┤
312        //                                                                                    │
313        //                       "owned" discriminant                                         │
314        //                       bitmask (any X bit)       [X X X ? ? ? ? ?] ◀────────────────┘
315        //
316        // Given that we know an inlined string cannot be any longer than the inline capacity, we know that the
317        // top-most bit in the tag byte can never be set, as it would imply a length of _at least_ 128. With that, we
318        // utilize invariant #3 of `Inner` -- allocations can never be larger than `isize::MAX` -- which lets us
319        // safely "tag" an owned string's capacity -- setting the upper most bit to 1 -- to indicate that it's an
320        // owned string.
321
322        // If the top bit is set, we know we're dealing with an owned string.
323        let tag_byte = self.data[INLINED_STR_TAG_INDEX];
324
325        if is_tagged(tag_byte) {
326            return UnionType::Owned;
327        }
328
329        // The top-most bit has to be set for an owned string, but isn't set for any other type, so try differentiating
330        // at this point.
331        match tag_byte {
332            // Empty string. Easy.
333            0 => UnionType::Empty,
334
335            // Anything between 1 and INLINED_STR_MAX_LEN, inclusive, is an inlined string.
336            1..=INLINED_STR_MAX_LEN_U8 => UnionType::Inlined,
337
338            // These are fixed values above the inline capacity and below the owned-string tag bit, so we just match
339            // them directly.
340            UNION_TYPE_TAG_VALUE_STATIC => UnionType::Static,
341            UNION_TYPE_TAG_VALUE_INTERNED_FIXED_SIZE => UnionType::InternedFixedSize,
342            UNION_TYPE_TAG_VALUE_INTERNED_GENERIC_MAP => UnionType::InternedGenericMap,
343            UNION_TYPE_TAG_VALUE_SHARED => UnionType::Shared,
344
345            // If we haven't matched any specific type tag value, then this is something else that we don't handle or
346            // know about... which we handle as just acting like we're an empty string for simplicity.
347            _ => UnionType::Empty,
348        }
349    }
350}
351
352/// The core data structure for holding all different string variants.
353///
354/// This union has six data fields -- one for each possible string variant -- and a discriminant field, used to
355/// determine which string variant is actually present. The discriminant field interrogates the bits in each machine
356/// word field (all variants are three machine words) to determine which bit patterns are valid or not for a given
357/// variant, allowing the string variant to be authoritatively determined.
358///
359/// # Invariants
360///
361/// This code depends on a number of invariants in order to work correctly:
362///
363/// 1. Only used on 64-bit little- or big-endian platforms. (checked at compile-time via _INVARIANTS_CHECK)
364/// 2. The data pointers for `String` and `&'static str` can't ever be null when the strings are non-empty.
365/// 3. Allocations can never be larger than `isize::MAX` (see [here][rust_isize_alloc_limit]), meaning that any
366///    length/capacity field for a string can't ever be larger than `isize::MAX`, implying the highest bit for
367///    length/capacity should always be 0.
368/// 4. An inlined string can only hold bytes before the endian-specific tag byte, meaning that the length byte for that
369///    string can never exceed the inline capacity. (_We_ have to provide this invariant, which is handled in
370///    `Inner::try_inlined`.)
371///
372/// [rust_isize_alloc_limit]: https://doc.rust-lang.org/stable/std/alloc/struct.Layout.html#method.from_size_align
373union Inner {
374    empty: EmptyUnion,
375    owned: OwnedUnion,
376    static_: StaticUnion,
377    interned: ManuallyDrop<InternedUnion>,
378    shared: ManuallyDrop<SharedUnion>,
379    inlined: InlinedUnion,
380    discriminant: DiscriminantUnion,
381}
382
383impl Inner {
384    const fn empty() -> Self {
385        Self {
386            empty: EmptyUnion::new(),
387        }
388    }
389
390    fn owned(value: String) -> Self {
391        match value.capacity() {
392            // The string we got is empty.
393            0 => Self::empty(),
394            cap => {
395                let mut value = value.into_bytes();
396
397                let ptr = value.as_mut_ptr();
398                let len = value.len();
399
400                // We're taking ownership of the underlying string allocation so we can't let it drop.
401                std::mem::forget(value);
402
403                Self {
404                    owned: OwnedUnion {
405                        ptr,
406                        len,
407                        cap: tag_cap(cap),
408                    },
409                }
410            }
411        }
412    }
413
414    const fn static_str(value: &'static str) -> Self {
415        match value.len() {
416            0 => Self::empty(),
417            _ => Self {
418                static_: StaticUnion {
419                    value,
420                    _cap: Tag::Static,
421                },
422            },
423        }
424    }
425
426    fn interned(value: InternedString) -> Self {
427        match value.len() {
428            0 => Self::empty(),
429            _len => {
430                let (state, tag) = match value.into_state() {
431                    InternedStringState::FixedSize(fixed_size) => (
432                        InternedStateUnion {
433                            fixed_size: ManuallyDrop::new(fixed_size),
434                        },
435                        Tag::InternedFixedSize,
436                    ),
437                    InternedStringState::GenericMap(generic_map) => (
438                        InternedStateUnion {
439                            generic_map: ManuallyDrop::new(generic_map),
440                        },
441                        Tag::InternedGenericMap,
442                    ),
443                };
444
445                Self {
446                    interned: ManuallyDrop::new(InternedUnion { state, _cap: tag }),
447                }
448            }
449        }
450    }
451
452    fn shared(value: Arc<str>) -> Self {
453        match value.len() {
454            0 => Self::empty(),
455            _len => Self {
456                shared: ManuallyDrop::new(SharedUnion {
457                    // SAFETY: We know `ptr` is non-null because `Arc::into_raw` is called on a valid `Arc<str>`.
458                    ptr: unsafe { NonNull::new_unchecked(Arc::into_raw(value).cast_mut()) },
459                    _cap: Tag::Shared,
460                }),
461            },
462        }
463    }
464
465    fn try_inlined(value: &str) -> Option<Self> {
466        match value.len() {
467            0 => Some(Self::empty()),
468            len => {
469                if len > INLINED_STR_MAX_LEN {
470                    return None;
471                }
472
473                let mut data = [0; INLINED_STR_DATA_BUF_LEN];
474
475                // SAFETY: We know it fits because we just checked that the string length is within the inline capacity.
476                data[INLINED_STR_MAX_LEN] = len as u8;
477
478                let buf = value.as_bytes();
479                data[0..len].copy_from_slice(buf);
480
481                Some(Self {
482                    inlined: InlinedUnion { data },
483                })
484            }
485        }
486    }
487
488    #[inline]
489    fn as_str(&self) -> &str {
490        let union_type = unsafe { self.discriminant.get_union_type() };
491        match union_type {
492            UnionType::Empty => "",
493            UnionType::Owned => {
494                let owned = unsafe { &self.owned };
495                owned.as_str()
496            }
497            UnionType::Static => {
498                let static_ = unsafe { &self.static_ };
499                static_.as_str()
500            }
501            UnionType::InternedFixedSize => {
502                let interned = unsafe { &self.interned.state.fixed_size };
503                interned.as_str()
504            }
505            UnionType::InternedGenericMap => {
506                let interned = unsafe { &self.interned.state.generic_map };
507                interned.as_str()
508            }
509            UnionType::Shared => {
510                let shared = unsafe { &self.shared };
511                shared.as_str()
512            }
513            UnionType::Inlined => {
514                let inlined = unsafe { &self.inlined };
515                inlined.as_str()
516            }
517        }
518    }
519
520    #[inline]
521    const fn get_union_type(&self) -> UnionType {
522        unsafe { self.discriminant.get_union_type() }
523    }
524
525    fn into_owned(mut self) -> String {
526        let union_type = unsafe { self.discriminant.get_union_type() };
527        match union_type {
528            UnionType::Empty => String::new(),
529            UnionType::Owned => {
530                // We're (`Inner`) being consumed here, but we need to update our internal state to ensure that our drop
531                // logic doesn't try to double free the string allocation.
532                let owned = unsafe { self.owned };
533                self.empty = EmptyUnion::new();
534
535                owned.into_owned()
536            }
537            UnionType::Static => {
538                let static_ = unsafe { self.static_ };
539                static_.as_str().to_owned()
540            }
541            UnionType::InternedFixedSize => {
542                let interned = unsafe { &self.interned.state.fixed_size };
543                interned.as_str().to_owned()
544            }
545            UnionType::InternedGenericMap => {
546                let interned = unsafe { &self.interned.state.generic_map };
547                interned.as_str().to_owned()
548            }
549            UnionType::Shared => {
550                let shared = unsafe { &self.shared };
551                shared.as_str().to_owned()
552            }
553            UnionType::Inlined => {
554                let inlined = unsafe { self.inlined };
555                inlined.as_str().to_owned()
556            }
557        }
558    }
559}
560
561impl Drop for Inner {
562    fn drop(&mut self) {
563        let union_type = unsafe { self.discriminant.get_union_type() };
564        match union_type {
565            UnionType::Owned => {
566                let owned = unsafe { &mut self.owned };
567
568                let ptr = owned.ptr;
569                let len = owned.len;
570                let cap = untag_cap(owned.cap);
571
572                // SAFETY: The pointer has to be non-null, because we only ever construct an owned variant when the
573                // `String` has a non-zero capacity, which implies a valid allocation, and thus a valid, non-null
574                // pointer.
575                let data = unsafe { Vec::<u8>::from_raw_parts(ptr, len, cap) };
576                drop(data);
577            }
578            UnionType::InternedFixedSize | UnionType::InternedGenericMap => {
579                let interned = unsafe { &mut self.interned };
580
581                // SAFETY: We're already dropping `Inner`, so nothing else can use the `InternedString` value after we
582                // consume it here.
583                let data = unsafe { ManuallyDrop::take(interned) };
584                drop(data);
585            }
586            UnionType::Shared => {
587                let shared = unsafe { &mut self.shared };
588
589                // Decrement the strong count before we drop, ensuring the `Arc` has a chance to clean itself up if this
590                // is the less strong reference.
591                //
592                // SAFETY: We know `shared.ptr` was obtained from `Arc::into_raw`, so it's valid to decrement on. We
593                // also know the backing storage is still live because it has to be by virtue of us being here.
594                unsafe {
595                    Arc::decrement_strong_count(shared.ptr.as_ptr().cast_const());
596                }
597            }
598            _ => {}
599        }
600    }
601}
602
603impl Clone for Inner {
604    fn clone(&self) -> Self {
605        let union_type = unsafe { self.discriminant.get_union_type() };
606        match union_type {
607            UnionType::Empty => Self::empty(),
608            UnionType::Owned => {
609                let owned = unsafe { self.owned };
610                let s = owned.as_str();
611
612                // We specifically try to inline here.
613                //
614                // At a high-level, when we're _given_ an owned string, we avoid inlining because we don't want to trash
615                // the underlying allocation since we may be asked to give it back later (via `MetaString::into_owned`).
616                // However, when we're _cloning_, we'd rather avoiding allocating if we can help it.
617                Self::try_inlined(s).unwrap_or_else(|| Self::owned(s.to_owned()))
618            }
619            UnionType::Static => Self {
620                static_: unsafe { self.static_ },
621            },
622            UnionType::InternedFixedSize | UnionType::InternedGenericMap => Self {
623                interned: unsafe { self.interned.clone() },
624            },
625            UnionType::Shared => {
626                let shared = unsafe { &self.shared };
627
628                // We have to increment the strong count before cloning.
629                //
630                // SAFETY: We know `shared.ptr` was obtained from `Arc::into_raw`. We also know that if we're cloning
631                // this value, that the underlying `Arc` must still be live, since we're holding a reference to it.
632                unsafe {
633                    Arc::increment_strong_count(shared.ptr.as_ptr().cast_const());
634                }
635
636                Self {
637                    shared: ManuallyDrop::new(SharedUnion {
638                        ptr: shared.ptr,
639                        _cap: Tag::Shared,
640                    }),
641                }
642            }
643            UnionType::Inlined => Self {
644                inlined: unsafe { self.inlined },
645            },
646        }
647    }
648}
649
650// SAFETY: None of our union variants are tied to the original thread they were created on.
651unsafe impl Send for Inner {}
652
653// SAFETY: None of our union variants use any form of interior mutability and are thus safe to be shared between
654// threads.
655unsafe impl Sync for Inner {}
656
657/// An immutable string type that abstracts over various forms of string storage.
658///
659/// Normally, developers will work with either `String` (owned) or `&str` (borrowed) when dealing with strings. In some
660/// cases, though, it can be useful to work with strings that use alternative storage, such as those that are atomically
661/// shared (for example, `InternedString`). While using those string types themselves isn't complex, using them and
662/// _also_ supporting normal string types can be complex.
663///
664/// `MetaString` is an opinionated string type that abstracts over the normal string types like `String` and `&str`
665/// while also supporting alternative storage, such as interned strings from `FixedSizeInterner`, unifying these
666/// different variants behind a single concrete type.
667///
668/// ## Supported types
669///
670/// `MetaString` supports the following "variants":
671///
672/// - owned (`String` and non-inlineable `&str`)
673/// - static (`&'static str`)
674/// - interned (`InternedString`)
675/// - shared (`Arc<str>`)
676/// - inlined (up to 23 bytes on little-endian platforms, and up to 16 bytes on big-endian platforms)
677///
678/// ### Owned and borrowed strings
679///
680/// `MetaString` can be created from `String` and `&str` directly. For owned scenarios (`String`), the string value is
681/// simply wrapped. For borrowed strings, we attempt to inline them (see more below) or, if they can't be inlined, they
682/// are copied into a new `String`.
683///
684/// ### Static strings
685///
686/// `MetaString` can be created from `&'static str` directly. This is useful for string literals and other static
687/// strings that are too large to be inlined.
688///
689/// ### Interned strings
690///
691/// `MetaString` can also be created from `InternedString`, which is a string that has been interned (using an interner
692/// like [`FixedSizeInterner`][crate::interning::FixedSizeInterner] or
693/// [`GenericMapInterner`][crate::interning::GenericMapInterner]). Interned strings are essentially a combination of the
694/// properties of `Arc<T>` -- owned wrappers around an atomically reference counted piece of data -- and a fixed-size
695/// buffer, where we allocate one large buffer, and write many small strings into it, and provide references to those
696/// strings through `InternedString`.
697///
698/// ### Shared strings
699///
700/// `MetaString` can be created from `Arc<str>`, which is a string slice that can be atomically shared between threads.
701/// This is a simpler version of interned strings where strict memory control and re-use isn't required.
702///
703/// ### Inlined strings
704///
705/// Finally, `MetaString` can also be created by inlining small strings into `MetaString` itself, avoiding the need for
706/// any backing allocation. "Small string optimization" is a common optimization for string types where small strings
707/// can be stored directly in a string type itself by utilizing a "union"-style layout.
708///
709/// As `MetaString` utilizes such a layout, we can provide a small string optimization that allows for strings up to 23
710/// bytes in length on little-endian platforms, and up to 16 bytes in length on big-endian platforms.
711///
712/// ## Conversion methods
713///
714/// Implementations of `From<T>` exist for all of the aforementioned types to allow for easily converting to
715/// `MetaString`. Once a caller has a `MetaString` value, they're generally expected to interact with the string in a
716/// read-only way, as `MetaString` can be dereferenced directly to `&str`.
717///
718/// If a caller needs to be able to modify the string data, they can call `into_owned` to get an owned version of the
719/// string, make their modifications to the owned version, and then convert that back to `MetaString`.
720#[derive(Clone)]
721pub struct MetaString {
722    inner: Inner,
723}
724
725impl MetaString {
726    /// Creates an empty `MetaString`.
727    ///
728    /// This doesn't allocate.
729    pub const fn empty() -> Self {
730        Self { inner: Inner::empty() }
731    }
732
733    /// Creates a new `MetaString` from the given static string.
734    ///
735    /// This doesn't allocate.
736    pub const fn from_static(s: &'static str) -> Self {
737        Self {
738            inner: Inner::static_str(s),
739        }
740    }
741
742    /// Attempts to create a new `MetaString` from the given string if it can be inlined.
743    pub fn try_inline(s: &str) -> Option<Self> {
744        Inner::try_inlined(s).map(|inner| Self { inner })
745    }
746
747    /// Creates a new `MetaString` from the given string, using the provided interner.
748    ///
749    /// The string is inlined if possible. If it can't be inlined, the interner is tried. If interning fails, an owned
750    /// string is allocated.
751    pub fn from_interner<I>(s: &str, interner: &I) -> Self
752    where
753        I: Interner,
754    {
755        if let Some(inlined) = Self::try_inline(s) {
756            return inlined;
757        }
758        if let Some(interned) = interner.try_intern(s) {
759            return Self::from(interned);
760        }
761        Self::from(s.to_owned())
762    }
763
764    /// Returns `true` if `self` has a length of zero bytes.
765    pub fn is_empty(&self) -> bool {
766        self.deref().is_empty()
767    }
768
769    /// Returns `true` if `self` can be cheaply cloned.
770    pub const fn is_cheaply_cloneable(&self) -> bool {
771        // If we're wrapping an owned string, cloning means cloning that allocation. All other types are cheap to clone.
772        !self.inner.get_union_type().is_owned()
773    }
774
775    /// Consumes `self` and returns an owned `String`.
776    ///
777    /// If the `MetaString` is already owned, this will simply return the inner `String` directly. Otherwise, this will
778    /// allocate an owned version (`String`) of the string data.
779    pub fn into_owned(self) -> String {
780        self.inner.into_owned()
781    }
782}
783
784impl Default for MetaString {
785    fn default() -> Self {
786        Self::empty()
787    }
788}
789
790impl hash::Hash for MetaString {
791    fn hash<H: hash::Hasher>(&self, state: &mut H) {
792        self.deref().hash(state)
793    }
794}
795
796impl PartialEq<str> for MetaString {
797    fn eq(&self, other: &str) -> bool {
798        self.deref() == other
799    }
800}
801
802impl PartialEq<&str> for MetaString {
803    fn eq(&self, other: &&str) -> bool {
804        self.deref() == *other
805    }
806}
807
808impl PartialEq<MetaString> for MetaString {
809    fn eq(&self, other: &MetaString) -> bool {
810        self.deref() == other.deref()
811    }
812}
813
814impl Eq for MetaString {}
815
816impl PartialOrd for MetaString {
817    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
818        Some(self.cmp(other))
819    }
820}
821
822impl Ord for MetaString {
823    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
824        self.deref().cmp(other.deref())
825    }
826}
827
828impl Borrow<str> for MetaString {
829    fn borrow(&self) -> &str {
830        self.deref()
831    }
832}
833
834impl Serialize for MetaString {
835    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
836    where
837        S: serde::Serializer,
838    {
839        serializer.serialize_str(self.deref())
840    }
841}
842
843impl<'de> Deserialize<'de> for MetaString {
844    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
845    where
846        D: serde::Deserializer<'de>,
847    {
848        struct MetaStringVisitor;
849
850        impl<'de> serde::de::Visitor<'de> for MetaStringVisitor {
851            type Value = MetaString;
852
853            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
854                formatter.write_str("a string")
855            }
856
857            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
858            where
859                E: serde::de::Error,
860            {
861                Ok(MetaString::from(v))
862            }
863        }
864
865        deserializer.deserialize_str(MetaStringVisitor)
866    }
867}
868
869impl From<String> for MetaString {
870    fn from(s: String) -> Self {
871        Self { inner: Inner::owned(s) }
872    }
873}
874
875impl From<&str> for MetaString {
876    fn from(s: &str) -> Self {
877        Self::try_inline(s).unwrap_or_else(|| Self::from(s.to_owned()))
878    }
879}
880
881impl From<InternedString> for MetaString {
882    fn from(s: InternedString) -> Self {
883        Self {
884            inner: Inner::interned(s),
885        }
886    }
887}
888
889impl From<Arc<str>> for MetaString {
890    fn from(s: Arc<str>) -> Self {
891        Self {
892            inner: Inner::shared(s),
893        }
894    }
895}
896
897impl From<MetaString> for protobuf::Chars {
898    fn from(value: MetaString) -> Self {
899        value.into_owned().into()
900    }
901}
902
903impl<'a> From<&'a MetaString> for protobuf::Chars {
904    fn from(value: &'a MetaString) -> Self {
905        // TODO: We're foregoing additional code/complexity to recover a static reference, when our string storage is
906        // static, in order to optimize the conversion to `Bytes` and then `Chars`.
907        //
908        // Static strings being written to Protocol Buffers should be decently rare across the codebase, so no biggie
909        // for now.
910        value.deref().into()
911    }
912}
913
914impl Deref for MetaString {
915    type Target = str;
916
917    fn deref(&self) -> &str {
918        self.inner.as_str()
919    }
920}
921
922impl AsRef<str> for MetaString {
923    fn as_ref(&self) -> &str {
924        self.deref()
925    }
926}
927
928impl fmt::Debug for MetaString {
929    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
930        self.deref().fmt(f)
931    }
932}
933
934impl fmt::Display for MetaString {
935    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
936        self.deref().fmt(f)
937    }
938}
939
940#[cfg(test)]
941mod tests {
942    use std::{num::NonZeroUsize, sync::Arc};
943
944    use proptest::{prelude::*, proptest};
945
946    use super::{
947        interning::GenericMapInterner, CheapMetaString, InlinedUnion, Inner, MetaString, UnionType,
948        INLINED_STR_MAX_LEN, INLINED_STR_TAG_INDEX, UNION_TYPE_TAG_VALUE_INTERNED_FIXED_SIZE,
949        UNION_TYPE_TAG_VALUE_INTERNED_GENERIC_MAP, UNION_TYPE_TAG_VALUE_SHARED, UNION_TYPE_TAG_VALUE_STATIC,
950    };
951    use crate::interning::{FixedSizeInterner, Interner as _};
952
953    fn discriminant_byte(inner: &Inner) -> u8 {
954        unsafe { inner.discriminant.data[INLINED_STR_TAG_INDEX] }
955    }
956
957    #[test]
958    fn struct_sizes() {
959        // Overall, we expect `MetaString`, and thus `Inner`, to always be three machine words.
960        assert_eq!(std::mem::size_of::<MetaString>(), std::mem::size_of::<usize>() * 3);
961        assert_eq!(std::mem::size_of::<Inner>(), std::mem::size_of::<usize>() * 3);
962
963        // We also expect all of inlined union variant to be the exact size of `Inner`, which means we're properly
964        // maximizing the available space for inlining.
965        assert_eq!(std::mem::size_of::<InlinedUnion>(), std::mem::size_of::<Inner>());
966    }
967
968    #[test]
969    fn inline_capacity_matches_target_endianness() {
970        let expected_inline_capacity = if cfg!(target_endian = "little") { 23 } else { 16 };
971
972        assert_eq!(INLINED_STR_MAX_LEN, expected_inline_capacity);
973    }
974
975    #[test]
976    fn discriminant_byte_tracks_endian_specific_variant_tags() {
977        let owned = MetaString::from(String::from("owned-value"));
978        assert_eq!(owned.inner.get_union_type(), UnionType::Owned);
979        assert!(discriminant_byte(&owned.inner) & 0b1000_0000 != 0);
980
981        let static_value = MetaString::from_static("static-value");
982        assert_eq!(static_value.inner.get_union_type(), UnionType::Static);
983        assert_eq!(discriminant_byte(&static_value.inner), UNION_TYPE_TAG_VALUE_STATIC);
984
985        let shared = MetaString::from(Arc::<str>::from("shared-value"));
986        assert_eq!(shared.inner.get_union_type(), UnionType::Shared);
987        assert_eq!(discriminant_byte(&shared.inner), UNION_TYPE_TAG_VALUE_SHARED);
988
989        let fixed_size_interner = FixedSizeInterner::<1>::new(NonZeroUsize::new(1024).unwrap());
990        let fixed_size = MetaString::from(fixed_size_interner.try_intern("fixed-size-value").unwrap());
991        assert_eq!(fixed_size.inner.get_union_type(), UnionType::InternedFixedSize);
992        assert_eq!(
993            discriminant_byte(&fixed_size.inner),
994            UNION_TYPE_TAG_VALUE_INTERNED_FIXED_SIZE
995        );
996
997        let generic_map_interner = GenericMapInterner::new(NonZeroUsize::new(1024).unwrap());
998        let generic_map = MetaString::from(generic_map_interner.try_intern("generic-map-value").unwrap());
999        assert_eq!(generic_map.inner.get_union_type(), UnionType::InternedGenericMap);
1000        assert_eq!(
1001            discriminant_byte(&generic_map.inner),
1002            UNION_TYPE_TAG_VALUE_INTERNED_GENERIC_MAP
1003        );
1004    }
1005
1006    #[test]
1007    fn inlined_string_uses_every_byte_before_tag_byte() {
1008        let input = "a".repeat(INLINED_STR_MAX_LEN);
1009        let meta = MetaString::try_inline(&input).expect("input should fit exactly in inline storage");
1010
1011        assert_eq!(meta.inner.get_union_type(), UnionType::Inlined);
1012        assert_eq!(discriminant_byte(&meta.inner), INLINED_STR_MAX_LEN as u8);
1013        let inlined = unsafe { meta.inner.inlined };
1014        assert_eq!(&inlined.data[..INLINED_STR_MAX_LEN], input.as_bytes());
1015    }
1016
1017    #[test]
1018    fn inlined_string_rejects_one_byte_past_tag_byte() {
1019        let input = "a".repeat(INLINED_STR_MAX_LEN + 1);
1020
1021        assert!(MetaString::try_inline(&input).is_none());
1022    }
1023
1024    #[test]
1025    fn static_str_inlineable() {
1026        // We always use the static variant, even if the string is inlineable, because this lets us make
1027        // `from_static` const.
1028        let s = "hello";
1029        let meta = MetaString::from_static(s);
1030
1031        assert_eq!(meta.inner.get_union_type(), UnionType::Static);
1032        assert_eq!(s, &*meta);
1033        assert_eq!(s, meta.into_owned());
1034    }
1035
1036    #[test]
1037    fn static_str_not_inlineable() {
1038        let s = "hello there, world! it's me, margaret!";
1039        let meta = MetaString::from_static(s);
1040
1041        assert_eq!(s, &*meta);
1042        assert_eq!(meta.inner.get_union_type(), UnionType::Static);
1043        assert_eq!(s, meta.into_owned());
1044    }
1045
1046    #[test]
1047    fn owned_string() {
1048        let s_orig = "hello";
1049        let s = String::from(s_orig);
1050        let meta = MetaString::from(s);
1051
1052        assert_eq!(s_orig, &*meta);
1053        assert_eq!(meta.inner.get_union_type(), UnionType::Owned);
1054        assert_eq!(s_orig, meta.into_owned());
1055    }
1056
1057    #[test]
1058    fn inlined_string() {
1059        let s = "hello";
1060        let meta = MetaString::from(s);
1061
1062        assert_eq!(s, &*meta);
1063        assert_eq!(meta.inner.get_union_type(), UnionType::Inlined);
1064        assert_eq!(s, meta.into_owned());
1065    }
1066
1067    #[test]
1068    fn interned_string() {
1069        let intern_str = "hello interned str!";
1070
1071        let fs_interner = FixedSizeInterner::<1>::new(NonZeroUsize::new(1024).unwrap());
1072        let s = fs_interner.try_intern(intern_str).unwrap();
1073        assert_eq!(intern_str, &*s);
1074
1075        let meta = MetaString::from(s);
1076        assert_eq!(intern_str, &*meta);
1077        assert_eq!(meta.inner.get_union_type(), UnionType::InternedFixedSize);
1078        assert_eq!(intern_str, meta.into_owned());
1079
1080        let gm_interner = GenericMapInterner::new(NonZeroUsize::new(1024).unwrap());
1081        let s = gm_interner.try_intern(intern_str).unwrap();
1082        assert_eq!(intern_str, &*s);
1083
1084        let meta = MetaString::from(s);
1085        assert_eq!(intern_str, &*meta);
1086        assert_eq!(meta.inner.get_union_type(), UnionType::InternedGenericMap);
1087        assert_eq!(intern_str, meta.into_owned());
1088    }
1089
1090    #[test]
1091    fn shared_string() {
1092        let shared_str = "hello shared str!";
1093
1094        let s = Arc::<str>::from(shared_str);
1095        assert_eq!(shared_str, &*s);
1096
1097        let meta = MetaString::from(s);
1098        assert_eq!(shared_str, &*meta);
1099        assert_eq!(meta.inner.get_union_type(), UnionType::Shared);
1100        assert_eq!(shared_str, meta.into_owned());
1101    }
1102
1103    #[test]
1104    fn meta_string_sorts_by_string_value() {
1105        let mut values = [
1106            MetaString::from("gamma"),
1107            MetaString::from("alpha"),
1108            MetaString::from("beta"),
1109        ];
1110        values.sort();
1111
1112        assert_eq!(
1113            values.iter().map(|value| &**value).collect::<Vec<_>>(),
1114            ["alpha", "beta", "gamma"]
1115        );
1116    }
1117
1118    #[test]
1119    fn protobuf_chars_conversions_preserve_value() {
1120        let chars: protobuf::Chars = MetaString::from("protobuf-value").into();
1121        let chars_ref: &str = chars.as_ref();
1122        assert_eq!(chars_ref, "protobuf-value");
1123
1124        let meta = MetaString::from("protobuf-ref-value");
1125        let chars: protobuf::Chars = (&meta).into();
1126        let chars_ref: &str = chars.as_ref();
1127        assert_eq!(chars_ref, "protobuf-ref-value");
1128    }
1129
1130    #[test]
1131    fn cheap_meta_string_trait_reports_cheap_clone_availability() {
1132        let shared = MetaString::from(Arc::<str>::from("shared-value"));
1133        assert_eq!(shared.try_cheap_clone().as_deref(), Some("shared-value"));
1134
1135        let owned = MetaString::from(String::from("owned-value"));
1136        assert!(owned.try_cheap_clone().is_none());
1137    }
1138
1139    #[test]
1140    fn shared_string_clone() {
1141        let shared_str = "hello shared str!";
1142        let s = Arc::<str>::from(shared_str);
1143        let meta = MetaString::from(s);
1144
1145        // Clone the `MetaString` to make sure we can still access the original string.
1146        let meta2 = meta.clone();
1147        assert_eq!(shared_str, &*meta2);
1148
1149        // Drop the original `MetaString` to ensure we can still access the string from our clone after going through
1150        // the drop logic for the shared variant.
1151        drop(meta);
1152        assert_eq!(shared_str, &*meta2);
1153    }
1154
1155    #[test]
1156    fn empty_string_shared() {
1157        let shared_str = "";
1158
1159        let s = Arc::<str>::from(shared_str);
1160        assert_eq!(shared_str, &*s);
1161
1162        let meta = MetaString::from(s);
1163        assert_eq!(shared_str, &*meta);
1164        assert_eq!(meta.inner.get_union_type(), UnionType::Empty);
1165        assert_eq!(shared_str, meta.into_owned());
1166    }
1167
1168    #[test]
1169    fn empty_string_interned() {
1170        let intern_str = "";
1171
1172        let interner = GenericMapInterner::new(NonZeroUsize::new(1024).unwrap());
1173        let s = interner.try_intern(intern_str).unwrap();
1174        assert_eq!(intern_str, &*s);
1175
1176        let meta = MetaString::from(s);
1177        assert_eq!(intern_str, &*meta);
1178        assert_eq!(meta.inner.get_union_type(), UnionType::Empty);
1179        assert_eq!(intern_str, meta.into_owned());
1180    }
1181
1182    #[test]
1183    fn empty_string_static() {
1184        let s = "";
1185
1186        let meta = MetaString::from_static(s);
1187        assert_eq!(s, &*meta);
1188        assert_eq!(meta.inner.get_union_type(), UnionType::Empty);
1189        assert_eq!(s, meta.into_owned());
1190    }
1191
1192    #[test]
1193    fn empty_string_inlined() {
1194        let s = "";
1195
1196        let meta = MetaString::try_inline(s).expect("empty string definitely 'fits'");
1197        assert_eq!(s, &*meta);
1198        assert_eq!(meta.inner.get_union_type(), UnionType::Empty);
1199        assert_eq!(s, meta.into_owned());
1200    }
1201
1202    #[test]
1203    fn empty_string_owned() {
1204        // When a string has capacity, we don't care if it's actually empty or not, because we want to preserve the
1205        // allocation... so our string here is empty but _does_ have capacity.
1206        let s = String::with_capacity(32);
1207        let actual_cap = s.capacity();
1208
1209        let meta = MetaString::from(s);
1210        assert_eq!("", &*meta);
1211        assert_eq!(meta.inner.get_union_type(), UnionType::Owned);
1212
1213        let owned = meta.into_owned();
1214        assert_eq!(owned.capacity(), actual_cap);
1215        assert_eq!("", owned);
1216    }
1217
1218    #[test]
1219    fn empty_string_owned_zero_capacity() {
1220        // When a string has _no_ capacity, it's effectively empty, and we treat it that way.
1221        let s = String::new();
1222
1223        let meta = MetaString::from(s);
1224        assert_eq!("", &*meta);
1225        assert_eq!(meta.inner.get_union_type(), UnionType::Empty);
1226        assert_eq!("", meta.into_owned());
1227    }
1228
1229    #[test]
1230    fn is_cheaply_cloneable() {
1231        // Owned strings are never cheap to clone.
1232        let s =
1233            String::from("big ol' stringy string that can't be inlined and lives out its bleak existence in the heap");
1234        let ms = MetaString::from(s);
1235        assert!(!ms.is_cheaply_cloneable());
1236
1237        // Empty strings are always cheap to clone.
1238        let ms = MetaString::empty();
1239        assert!(ms.is_cheaply_cloneable());
1240
1241        // Inlined strings are always cheap to clone.
1242        let s = "hello";
1243        let ms = MetaString::try_inline(s).expect("inlined string should fit");
1244        assert!(ms.is_cheaply_cloneable());
1245
1246        // Static strings are always cheap to clone.
1247        let s = "hello there, world! it's me, margaret!";
1248        let ms = MetaString::from_static(s);
1249        assert!(ms.is_cheaply_cloneable());
1250
1251        // Interned strings are always cheap to clone.
1252        let interner = GenericMapInterner::new(NonZeroUsize::new(1024).unwrap());
1253        let s = interner.try_intern("hello interned str!").unwrap();
1254        let ms = MetaString::from(s);
1255        assert!(ms.is_cheaply_cloneable());
1256
1257        // Shared strings are always cheap to clone.
1258        let s = Arc::from("hello shared str!");
1259        let ms = MetaString::from(s);
1260        assert!(ms.is_cheaply_cloneable());
1261    }
1262
1263    /// A string long enough that it can neither be inlined (>23 bytes) nor mistaken for an inlined string, used to
1264    /// force the non-inlined `MetaString` variants (owned/static/interned/shared).
1265    const LONG: &str = "this string is definitely longer than twenty-three bytes";
1266
1267    fn hash_of(ms: &MetaString) -> u64 {
1268        use std::hash::{Hash as _, Hasher as _};
1269
1270        let mut hasher = std::collections::hash_map::DefaultHasher::new();
1271        ms.hash(&mut hasher);
1272        hasher.finish()
1273    }
1274
1275    #[test]
1276    fn serde_round_trips_every_variant() {
1277        // The custom `Serialize`/`Deserialize` (a hand-written `Visitor`) must survive a real format round-trip for
1278        // every backing variant. Deserialization always reconstructs an inlined/owned string, so the invariant is
1279        // content equality, not variant identity.
1280        let interner = GenericMapInterner::new(NonZeroUsize::new(1024).unwrap());
1281        let cases: Vec<(MetaString, &str)> = vec![
1282            (MetaString::empty(), ""),
1283            (MetaString::try_inline("hi").expect("fits inline"), "hi"),
1284            (MetaString::from_static(LONG), LONG),
1285            (MetaString::from(String::from(LONG)), LONG),
1286            (MetaString::from(interner.try_intern(LONG).expect("interns")), LONG),
1287            (MetaString::from(Arc::<str>::from(LONG)), LONG),
1288        ];
1289
1290        for (ms, expected) in cases {
1291            let json = serde_json::to_string(&ms).expect("serialization should succeed");
1292
1293            // Decoding the JSON as a plain `String` confirms the serialized form carries the raw string content.
1294            let as_plain: String = serde_json::from_str(&json).expect("serialized form should be a JSON string");
1295            assert_eq!(as_plain, expected, "serialized form should carry the string content");
1296
1297            let back: MetaString = serde_json::from_str(&json).expect("deserialization should succeed");
1298            assert_eq!(back, expected, "deserialized value should match the original content");
1299            assert_eq!(ms, back, "round-trip should preserve equality");
1300        }
1301    }
1302
1303    #[test]
1304    fn equal_content_hashes_equally_across_variants() {
1305        // `Hash` delegates to the string content, so two `MetaString`s holding identical bytes must hash equally even
1306        // when they use entirely different backing variants (otherwise they'd behave inconsistently as map keys).
1307        let interner = GenericMapInterner::new(NonZeroUsize::new(1024).unwrap());
1308        let variants = [
1309            MetaString::from_static(LONG),
1310            MetaString::from(String::from(LONG)),
1311            MetaString::from(interner.try_intern(LONG).expect("interns")),
1312            MetaString::from(Arc::<str>::from(LONG)),
1313        ];
1314
1315        // Confirm the fixtures really do exercise distinct backing variants.
1316        assert_eq!(variants[0].inner.get_union_type(), UnionType::Static);
1317        assert_eq!(variants[1].inner.get_union_type(), UnionType::Owned);
1318        assert_eq!(variants[2].inner.get_union_type(), UnionType::InternedGenericMap);
1319        assert_eq!(variants[3].inner.get_union_type(), UnionType::Shared);
1320
1321        let expected_hash = hash_of(&variants[0]);
1322        for variant in &variants {
1323            assert_eq!(
1324                hash_of(variant),
1325                expected_hash,
1326                "hash must depend only on content, not the backing variant"
1327            );
1328            assert_eq!(
1329                *variant, variants[0],
1330                "equal content must compare equal across variants"
1331            );
1332        }
1333
1334        // An inlined short string and a static short string with the same content must also agree.
1335        let inlined = MetaString::from("short");
1336        let static_ = MetaString::from_static("short");
1337        assert_eq!(inlined.inner.get_union_type(), UnionType::Inlined);
1338        assert_eq!(static_.inner.get_union_type(), UnionType::Static);
1339        assert_eq!(hash_of(&inlined), hash_of(&static_));
1340    }
1341
1342    #[test]
1343    fn from_interner_inlines_short_strings() {
1344        // Step 1 of the documented fallback: an inlineable string is inlined and the interner is never consulted.
1345        let interner = GenericMapInterner::new(NonZeroUsize::new(1024).unwrap());
1346        let ms = MetaString::from_interner("short", &interner);
1347
1348        assert_eq!(ms.inner.get_union_type(), UnionType::Inlined);
1349        assert_eq!(ms, "short");
1350        assert_eq!(interner.len(), 0, "inlining must not add an interner entry");
1351    }
1352
1353    #[test]
1354    fn from_interner_interns_non_inlineable_strings() {
1355        // Step 2 of the documented fallback: a non-inlineable string is interned when the interner has capacity.
1356        let interner = GenericMapInterner::new(NonZeroUsize::new(1024).unwrap());
1357        assert!(LONG.len() > INLINED_STR_MAX_LEN);
1358
1359        let ms = MetaString::from_interner(LONG, &interner);
1360
1361        assert_eq!(ms.inner.get_union_type(), UnionType::InternedGenericMap);
1362        assert_eq!(ms, LONG);
1363        assert_eq!(interner.len(), 1, "the string should have been interned");
1364    }
1365
1366    #[test]
1367    fn from_interner_falls_back_to_owned_when_interner_is_full() {
1368        // Step 3 of the documented fallback: a non-inlineable string is allocated as an owned string when interning
1369        // fails (here, because the interner is far too small to hold it).
1370        let interner = GenericMapInterner::new(NonZeroUsize::new(16).unwrap());
1371        assert!(LONG.len() > INLINED_STR_MAX_LEN);
1372
1373        let ms = MetaString::from_interner(LONG, &interner);
1374
1375        assert_eq!(ms.inner.get_union_type(), UnionType::Owned);
1376        assert_eq!(ms, LONG);
1377        assert_eq!(
1378            interner.len(),
1379            0,
1380            "the interner had no room, so nothing should be interned"
1381        );
1382    }
1383
1384    fn arb_unicode_str_max_len(max_len: usize) -> impl Strategy<Value = String> {
1385        ".{0,23}".prop_filter("resulting string is too long", move |s| s.len() <= max_len)
1386    }
1387
1388    proptest! {
1389        #![proptest_config(ProptestConfig::with_cases(10000))]
1390
1391        #[test]
1392        #[cfg_attr(miri, ignore)]
1393        fn property_test_inlined_string(
1394            input in arb_unicode_str_max_len(INLINED_STR_MAX_LEN),
1395        ) {
1396            assert!(input.len() <= INLINED_STR_MAX_LEN, "input should fit in the inline buffer");
1397            let meta = MetaString::try_inline(&input).expect("input should fit");
1398
1399            if input.is_empty() {
1400                assert_eq!(meta.inner.get_union_type(), UnionType::Empty);
1401            } else {
1402                assert_eq!(input, &*meta);
1403                assert_eq!(meta.inner.get_union_type(), UnionType::Inlined);
1404            }
1405        }
1406
1407        #[test]
1408        #[cfg_attr(miri, ignore)]
1409        fn property_test_owned_string(
1410            input in ".*",
1411        ) {
1412            let is_empty = input.is_empty();
1413            let meta = MetaString::from(input);
1414
1415            if is_empty {
1416                assert_eq!(meta.inner.get_union_type(), UnionType::Empty);
1417            } else {
1418                assert_eq!(meta.inner.get_union_type(), UnionType::Owned);
1419            }
1420        }
1421    }
1422}