libdd_profiling/profiles/collections/
thin_str.rs

1// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use libdd_alloc::{AllocError, Allocator};
5use std::alloc::Layout;
6use std::borrow::Borrow;
7use std::ffi::c_void;
8use std::marker::PhantomData;
9use std::mem::MaybeUninit;
10use std::ops::Deref;
11use std::ptr::NonNull;
12use std::{fmt, hash, ptr};
13
14const USIZE_WIDTH: usize = core::mem::size_of::<usize>();
15
16/// A struct which acts like a thin slice reference. It does this by storing
17// the length of the slice just before the elements of the slice.
18#[derive(Copy, Clone)]
19#[repr(C)]
20pub struct ThinSlice<'a, T: Copy> {
21    thin_ptr: ThinPtr<T>,
22
23    /// Since [`ThinSlice`] doesn't hold a reference but acts like one,
24    // indicate this to the compiler with phantom data.
25    // This takes up no space.
26    _marker: PhantomData<&'a [T]>,
27}
28
29impl<T: Copy + fmt::Debug> fmt::Debug for ThinSlice<'_, T> {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        self.deref().fmt(f)
32    }
33}
34
35/// A struct which acts like a thin &str. It does this by storing the size
36/// of the string just before the bytes of the string.
37#[derive(Copy, Clone)]
38#[repr(transparent)]
39pub struct ThinStr<'a> {
40    inner: ThinSlice<'a, u8>,
41}
42
43impl ThinStr<'_> {
44    pub fn into_raw(self) -> NonNull<c_void> {
45        self.inner.thin_ptr.size_ptr.cast()
46    }
47
48    /// Re-creates a [`ThinStr`] created by [`ThinStr::into_raw`].
49    ///
50    /// # Safety
51    ///
52    /// `this` needs to be created from [``ThinStr::into_raw`] and the storage
53    /// it belongs to should still be alive.
54    pub unsafe fn from_raw(this: NonNull<c_void>) -> Self {
55        // SAFETY: `this` must have been produced by `ThinStr::into_raw` for
56        // a compatible `ThinStr` allocation. After calling this function, the
57        // original raw pointer must not be used again.
58        let thin_ptr = ThinPtr {
59            size_ptr: this.cast(),
60            _marker: PhantomData,
61        };
62        Self {
63            inner: ThinSlice {
64                thin_ptr,
65                _marker: PhantomData,
66            },
67        }
68    }
69}
70
71impl fmt::Debug for ThinStr<'_> {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        self.deref().fmt(f)
74    }
75}
76
77// SAFETY: ThinStr is safe to send between threads as long as the underlying
78// arena/storage remains alive. The caller must ensure the arena outlives all
79// ThinStr references. This is the design trade-off for better performance
80// than individual reference counting.
81unsafe impl<T: Copy> Send for ThinPtr<T> {}
82unsafe impl<T: Copy> Sync for ThinPtr<T> {}
83
84unsafe impl<T: Copy> Send for ThinSlice<'_, T> {}
85unsafe impl<T: Copy> Sync for ThinSlice<'_, T> {}
86
87unsafe impl Send for ThinStr<'_> {}
88unsafe impl Sync for ThinStr<'_> {}
89
90impl ThinStr<'static> {
91    pub const fn new() -> ThinStr<'static> {
92        ThinStr {
93            inner: ThinSlice {
94                thin_ptr: EMPTY_INLINE_STRING.as_thin_ptr(),
95                _marker: PhantomData,
96            },
97        }
98    }
99
100    pub const fn end_timestamp_ns() -> ThinStr<'static> {
101        ThinStr {
102            inner: ThinSlice {
103                thin_ptr: END_TIMESTAMP_NS.as_thin_ptr(),
104                _marker: PhantomData,
105            },
106        }
107    }
108
109    pub const fn local_root_span_id() -> ThinStr<'static> {
110        ThinStr {
111            inner: ThinSlice {
112                thin_ptr: LOCAL_ROOT_SPAN_ID.as_thin_ptr(),
113                _marker: PhantomData,
114            },
115        }
116    }
117
118    pub const fn trace_endpoint() -> ThinStr<'static> {
119        ThinStr {
120            inner: ThinSlice {
121                thin_ptr: TRACE_ENDPOINT.as_thin_ptr(),
122                _marker: PhantomData,
123            },
124        }
125    }
126
127    pub const fn span_id() -> ThinStr<'static> {
128        ThinStr {
129            inner: ThinSlice {
130                thin_ptr: SPAN_ID.as_thin_ptr(),
131                _marker: PhantomData,
132            },
133        }
134    }
135}
136
137impl Default for ThinStr<'static> {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl<const N: usize> Borrow<InlineString> for ConstString<N> {
144    fn borrow(&self) -> &InlineString {
145        let thin_ptr = ThinPtr {
146            size_ptr: NonNull::from(self).cast::<u8>(),
147            _marker: PhantomData,
148        };
149        // SAFETY: the object is layout compatible and lifetime is safe, and
150        // inline strings are valid UTF-8.
151        unsafe { &*thin_ptr.inline_string_ptr().as_ptr() }
152    }
153}
154
155#[repr(transparent)]
156#[derive(Clone, Copy)]
157struct ThinPtr<T: Copy> {
158    /// Points to the beginning of an inline slice of T.
159    size_ptr: NonNull<u8>,
160    _marker: PhantomData<T>,
161}
162
163#[repr(C)]
164pub struct InlineSlice<T: Copy> {
165    /// Stores the len of `data` in native endian.
166    size: [u8; core::mem::size_of::<usize>()],
167    data: [T],
168}
169
170impl<T: Copy> Deref for InlineSlice<T> {
171    type Target = [T];
172    fn deref(&self) -> &Self::Target {
173        &self.data
174    }
175}
176
177#[repr(C)]
178pub struct InlineString {
179    /// Stores the len of `data` in native endian.
180    size: [u8; core::mem::size_of::<usize>()],
181    data: str,
182}
183
184impl Deref for InlineString {
185    type Target = str;
186    fn deref(&self) -> &Self::Target {
187        &self.data
188    }
189}
190
191impl<T: Copy> ThinPtr<T> {
192    /// Reads the size prefix to get the length of the slice.
193    const fn len(self) -> usize {
194        // SAFETY: ThinPtr points to the size prefix of the slice.
195        let size = unsafe { self.size_ptr.cast::<[u8; USIZE_WIDTH]>().as_ptr().read() };
196        usize::from_ne_bytes(size)
197    }
198
199    /// Returns a wide pointer to an inline slice. The pointer is mut but you
200    /// most likely shouldn't modify it.
201    const fn inline_slice_ptr(self) -> NonNull<InlineSlice<T>> {
202        let len = self.len();
203        let slice = ptr::slice_from_raw_parts_mut(self.size_ptr.as_ptr(), len);
204        // SAFETY: derived from a non-null pointer self.size_ptr.
205        unsafe { NonNull::new_unchecked(slice as *mut [()] as *mut InlineSlice<T>) }
206    }
207}
208
209impl ThinPtr<u8> {
210    /// Returns a wide pointer to an inline string. The pointer is mut but you
211    /// most likely shouldn't modify it.
212    ///
213    /// # Safety
214    /// The bytes must be valid UTF-8 and originate from a valid `InlineString`
215    /// layout created by this module.
216    const unsafe fn inline_string_ptr(self) -> NonNull<InlineString> {
217        let len = self.len();
218        let slice = ptr::slice_from_raw_parts_mut(self.size_ptr.as_ptr(), len);
219        // SAFETY: derived from a non-null pointer self.size_ptr.
220        unsafe { NonNull::new_unchecked(slice as *mut [()] as *mut InlineString) }
221    }
222}
223
224// Generic ThinSlice implementation
225impl<'a, T: Copy> ThinSlice<'a, T> {
226    /// Returns the length of the slice.
227    pub fn len(&self) -> usize {
228        self.thin_ptr.len()
229    }
230
231    /// Returns true if the slice is empty.
232    pub fn is_empty(&self) -> bool {
233        self.len() == 0
234    }
235
236    /// Returns the slice as a `&[T]`.
237    pub fn as_slice(&self) -> &[T] {
238        // SAFETY: ThinSlice is layout compatible with InlineSlice, and the
239        // lifetime is correct.
240        let inline_slice = unsafe { self.thin_ptr.inline_slice_ptr().as_ref() };
241        &inline_slice.data
242    }
243
244    /// Computes the layout for a slice of the given length.
245    pub fn layout_for(slice: &[T]) -> Result<Layout, AllocError> {
246        let len = slice.len();
247        let element_size = core::mem::size_of::<T>();
248        let data_size = len.checked_mul(element_size).ok_or(AllocError)?;
249        let total_size = USIZE_WIDTH.checked_add(data_size).ok_or(AllocError)?;
250        Layout::from_size_align(total_size, 1).map_err(|_| AllocError)
251    }
252
253    /// Allocates memory for a slice and returns a pointer to uninitialized memory.
254    pub fn try_allocate_for<A: Allocator>(
255        slice: &[T],
256        alloc: &A,
257    ) -> Result<NonNull<[MaybeUninit<u8>]>, AllocError> {
258        let layout = Self::layout_for(slice)?;
259        let obj = alloc.allocate(layout)?;
260        let ptr = obj.cast::<MaybeUninit<u8>>();
261        Ok(NonNull::slice_from_raw_parts(ptr, obj.len()))
262    }
263
264    /// Tries to create a [`ThinSlice`] in the uninitialized space.
265    ///
266    /// # Errors
267    ///
268    /// Returns an error if the spare capacity is not large enough.
269    pub fn try_from_slice_in(
270        slice: &[T],
271        spare_capacity: &'a mut [MaybeUninit<u8>],
272    ) -> Result<Self, AllocError> {
273        let layout = Self::layout_for(slice)?;
274        if spare_capacity.len() < layout.size() {
275            return Err(AllocError);
276        }
277
278        let allocation = spare_capacity.as_mut_ptr().cast::<u8>();
279
280        // Write the size prefix
281        let size_bytes = slice.len().to_ne_bytes();
282        // SAFETY: we've verified the allocation is big enough and aligned.
283        unsafe { core::ptr::copy_nonoverlapping(size_bytes.as_ptr(), allocation, USIZE_WIDTH) };
284
285        // Write the data
286        let data = unsafe { allocation.add(USIZE_WIDTH).cast::<T>() };
287        // SAFETY: the allocation is big enough, locations are distinct, and
288        // the memory is safe for writing.
289        unsafe { core::ptr::copy_nonoverlapping(slice.as_ptr(), data, slice.len()) };
290
291        let size_ptr = unsafe { NonNull::new_unchecked(allocation) };
292        let thin_ptr = ThinPtr {
293            size_ptr,
294            _marker: PhantomData,
295        };
296        let _marker = PhantomData;
297        Ok(ThinSlice { thin_ptr, _marker })
298    }
299
300    /// Creates a [`ThinSlice`] in the uninitialized space without checking capacity.
301    ///
302    /// # Safety
303    ///
304    /// The caller must ensure that `spare_capacity` has enough space for the slice
305    /// as determined by [`Self::layout_for`].
306    pub unsafe fn from_slice_in_unchecked(
307        slice: &[T],
308        spare_capacity: &'a mut [MaybeUninit<u8>],
309    ) -> Self {
310        let allocation = spare_capacity.as_mut_ptr().cast::<u8>();
311
312        // Write the size prefix
313        let size_bytes = slice.len().to_ne_bytes();
314        core::ptr::copy_nonoverlapping(size_bytes.as_ptr(), allocation, USIZE_WIDTH);
315
316        // Write the data
317        let data = unsafe { allocation.add(USIZE_WIDTH).cast::<T>() };
318        core::ptr::copy_nonoverlapping(slice.as_ptr(), data, slice.len());
319
320        let size_ptr = NonNull::new_unchecked(allocation);
321        let thin_ptr = ThinPtr {
322            size_ptr,
323            _marker: PhantomData,
324        };
325        let _marker = PhantomData;
326        ThinSlice { thin_ptr, _marker }
327    }
328
329    /// Returns the memory layout of this slice.
330    pub fn layout(&self) -> Layout {
331        // layout_for only fails on overflow or invalid align; for valid T and lengths
332        // produced by this type, it should always succeed. In case of error, fall back to
333        // a conservative layout that matches the actual allocation.
334        Self::layout_for(self.as_slice()).unwrap_or_else(|_| unsafe {
335            // Size = prefix + data length, with alignment of 1
336            Layout::from_size_align_unchecked(USIZE_WIDTH + self.len(), 1)
337        })
338    }
339}
340
341impl<T: Copy> Deref for ThinSlice<'_, T> {
342    type Target = [T];
343    fn deref(&self) -> &Self::Target {
344        self.as_slice()
345    }
346}
347
348impl<T: Copy> PartialEq for ThinSlice<'_, T>
349where
350    T: PartialEq,
351{
352    fn eq(&self, other: &Self) -> bool {
353        self.as_slice() == other.as_slice()
354    }
355}
356
357impl<T: Copy> Eq for ThinSlice<'_, T> where T: Eq {}
358
359impl<T: Copy> hash::Hash for ThinSlice<'_, T>
360where
361    T: hash::Hash,
362{
363    fn hash<H: hash::Hasher>(&self, state: &mut H) {
364        self.as_slice().hash(state)
365    }
366}
367
368impl<T: Copy> Borrow<[T]> for ThinSlice<'_, T> {
369    fn borrow(&self) -> &[T] {
370        self.as_slice()
371    }
372}
373
374impl<T: Copy> Borrow<InlineSlice<T>> for ThinSlice<'_, T> {
375    fn borrow(&self) -> &InlineSlice<T> {
376        // SAFETY: ThinSlice is layout compatible with InlineSlice, and the
377        // lifetime is correct.
378        unsafe { self.thin_ptr.inline_slice_ptr().as_ref() }
379    }
380}
381
382// String-specific ThinStr implementations that delegate to ThinSlice
383impl<'a> ThinStr<'a> {
384    // Note: len(), is_empty(), and as_bytes() are available through Deref<Target = str>
385
386    /// Computes the layout for a string of the given length.
387    pub fn layout_for(str: &str) -> Result<Layout, AllocError> {
388        ThinSlice::layout_for(str.as_bytes())
389    }
390
391    /// Allocates memory for a string and returns a pointer to uninitialized memory.
392    pub fn try_allocate_for<A: Allocator>(
393        str: &str,
394        alloc: &A,
395    ) -> Result<NonNull<[MaybeUninit<u8>]>, AllocError> {
396        ThinSlice::try_allocate_for(str.as_bytes(), alloc)
397    }
398
399    /// Tries to create a [`ThinStr`] in the uninitialized space.
400    ///
401    /// # Errors
402    ///
403    /// Returns an error if the spare capacity is not large enough.
404    pub fn try_from_str_in(
405        str: &str,
406        spare_capacity: &'a mut [MaybeUninit<u8>],
407    ) -> Result<Self, AllocError> {
408        let inner = ThinSlice::try_from_slice_in(str.as_bytes(), spare_capacity)?;
409        Ok(ThinStr { inner })
410    }
411
412    /// Creates a [`ThinStr`] in the uninitialized space without checking capacity.
413    ///
414    /// # Safety
415    ///
416    /// The caller must ensure that `spare_capacity` has enough space for the string
417    /// as determined by [`Self::layout_for`].
418    pub unsafe fn from_str_in_unchecked(
419        str: &str,
420        spare_capacity: &'a mut [MaybeUninit<u8>],
421    ) -> Self {
422        let inner = ThinSlice::from_slice_in_unchecked(str.as_bytes(), spare_capacity);
423        ThinStr { inner }
424    }
425
426    /// Returns the memory layout of this string.
427    pub fn layout(&self) -> Layout {
428        self.inner.layout()
429    }
430}
431
432impl Deref for ThinStr<'_> {
433    type Target = str;
434    fn deref(&self) -> &Self::Target {
435        let inline_string: &InlineString = self.borrow();
436        &inline_string.data
437    }
438}
439
440impl Borrow<str> for ThinStr<'_> {
441    fn borrow(&self) -> &str {
442        self.deref()
443    }
444}
445
446impl Borrow<InlineString> for ThinStr<'_> {
447    fn borrow(&self) -> &InlineString {
448        // SAFETY: as long as the lifetime is correct, then this is also safe.
449        // If the caller is lying about the lifetime (e.g. dynamic lifetimes)
450        // then the caller needs to be cautious about borrowing this, and
451        // ThinStr only stores valid UTF-8 strings.
452        unsafe { self.inner.thin_ptr.inline_string_ptr().as_ref() }
453    }
454}
455
456impl PartialEq for ThinStr<'_> {
457    fn eq(&self, other: &Self) -> bool {
458        self.deref() == other.deref()
459    }
460}
461
462impl Eq for ThinStr<'_> {}
463
464impl hash::Hash for ThinStr<'_> {
465    fn hash<H: hash::Hasher>(&self, state: &mut H) {
466        // Hash as a string to maintain consistency with &str
467        self.deref().hash(state)
468    }
469}
470
471impl<'a> From<ThinSlice<'a, u8>> for ThinStr<'a> {
472    fn from(inner: ThinSlice<'a, u8>) -> Self {
473        ThinStr { inner }
474    }
475}
476
477impl<'a> From<ThinStr<'a>> for ThinSlice<'a, u8> {
478    fn from(thin_str: ThinStr<'a>) -> Self {
479        thin_str.inner
480    }
481}
482
483/// [`ConstString`] is used to create the storage needed for static strings
484/// that back [`ThinStr`]s.
485#[repr(C)]
486pub struct ConstString<const N: usize> {
487    /// Stores the len of `data`.
488    size: [u8; core::mem::size_of::<usize>()],
489    data: [u8; N],
490}
491
492impl<const N: usize> ConstString<N> {
493    const fn new(str: &str) -> Self {
494        // Meant for complile-time validation.
495        #[allow(clippy::panic)]
496        if str.len() != N {
497            panic!("string length and storage mismatch for ConstString")
498        }
499        ConstString::<N> {
500            size: N.to_ne_bytes(),
501            data: {
502                let src = str.as_bytes();
503                let mut dst = [0u8; N];
504                let mut i = 0usize;
505                while i < N {
506                    dst[i] = src[i];
507                    i += 1;
508                }
509                dst
510            },
511        }
512    }
513    const fn as_thin_ptr(&self) -> ThinPtr<u8> {
514        let ptr = core::ptr::addr_of!(self.size).cast::<u8>();
515        // SAFETY: derived from static address, and ThinStr does not allow
516        // modifications, so the mut-cast is also fine.
517        let size_ptr = unsafe { NonNull::new_unchecked(ptr.cast_mut()) };
518        ThinPtr {
519            size_ptr,
520            _marker: PhantomData,
521        }
522    }
523}
524
525static EMPTY_INLINE_STRING: ConstString<0> = ConstString::new("");
526static END_TIMESTAMP_NS: ConstString<16> = ConstString::new("end_timestamp_ns");
527static LOCAL_ROOT_SPAN_ID: ConstString<18> = ConstString::new("local root span id");
528static TRACE_ENDPOINT: ConstString<14> = ConstString::new("trace endpoint");
529static SPAN_ID: ConstString<7> = ConstString::new("span id");
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534    use libdd_alloc::Global;
535
536    const TEST_STRINGS: [&str; 5] = [
537        "datadog",
538        "MyNamespace.MyClass.MyMethod(Int32 id, String name)",
539        "/var/run/datadog/apm.socket",
540        "[truncated]",
541        "Sidekiq::❨╯°□°❩╯︵┻━┻",
542    ];
543
544    #[test]
545    fn test_allocation_and_deallocation() {
546        let alloc = &Global;
547
548        let mut thin_strs: Vec<ThinStr> = TEST_STRINGS
549            .iter()
550            .copied()
551            .map(|str| {
552                let obj = ThinStr::try_allocate_for(str, alloc).unwrap();
553                // SAFETY: just allocated the bytes, no other references exist,
554                // so we can safely turn it into `&mut [MaybeUninit<u8>]`.
555                let uninit = unsafe { &mut *obj.as_ptr() };
556                let thin_str = ThinStr::try_from_str_in(str, uninit).unwrap();
557                let actual = thin_str.deref();
558                assert_eq!(str, actual);
559                thin_str
560            })
561            .collect();
562
563        // This could detect out-of-bounds reads.
564        for (thin_str, str) in thin_strs.iter().zip(TEST_STRINGS) {
565            let actual = thin_str.deref();
566            assert_eq!(str, actual);
567        }
568
569        for thin_str in thin_strs.drain(..) {
570            unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
571        }
572    }
573
574    #[test]
575    fn test_empty_string() {
576        let alloc = &Global;
577
578        let obj = ThinStr::try_allocate_for("", alloc).unwrap();
579        let uninit = unsafe { &mut *obj.as_ptr() };
580        let thin_str = ThinStr::try_from_str_in("", uninit).unwrap();
581
582        assert_eq!(thin_str.deref(), "");
583        assert_eq!(thin_str.deref().len(), 0);
584
585        unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
586    }
587
588    #[test]
589    fn test_single_byte_strings() {
590        let alloc = &Global;
591        let single_bytes = ["a", "z", "0", "9", "!", "~"];
592
593        for &s in &single_bytes {
594            let obj = ThinStr::try_allocate_for(s, alloc).unwrap();
595            let uninit = unsafe { &mut *obj.as_ptr() };
596            let thin_str = ThinStr::try_from_str_in(s, uninit).unwrap();
597
598            assert_eq!(thin_str.deref(), s);
599            assert_eq!(thin_str.deref().len(), 1);
600
601            unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
602        }
603    }
604
605    #[test]
606    fn test_boundary_lengths() {
607        let alloc = &Global;
608
609        // Test strings around common boundary sizes
610        let test_cases = [
611            ("", 0),
612            ("a", 1),
613            ("ab", 2),
614            ("abc", 3),
615            ("abcd", 4),
616            ("abcdefg", 7),
617            ("abcdefgh", 8),
618            ("abcdefghijklmno", 15),
619            ("abcdefghijklmnop", 16),
620            ("abcdefghijklmnopqrstuvwxyz123456", 32),
621            ("abcdefghijklmnopqrstuvwxyz1234567", 33),
622        ];
623
624        for (s, expected_len) in test_cases {
625            assert_eq!(s.len(), expected_len);
626
627            let obj = ThinStr::try_allocate_for(s, alloc).unwrap();
628            let uninit = unsafe { &mut *obj.as_ptr() };
629            let thin_str = ThinStr::try_from_str_in(s, uninit).unwrap();
630
631            assert_eq!(thin_str.deref(), s);
632            assert_eq!(thin_str.deref().len(), expected_len);
633
634            unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
635        }
636    }
637
638    #[test]
639    fn test_unicode_edge_cases() {
640        let alloc = &Global;
641
642        let unicode_cases = [
643            "é",                  // 2-byte UTF-8
644            "€",                  // 3-byte UTF-8
645            "🦀",                 // 4-byte UTF-8
646            "\u{0000}",           // Null character
647            "\u{FFFD}",           // Replacement character
648            "a\u{0000}b",         // Embedded null
649            "\n\r\t",             // Control characters
650            "\u{1F600}\u{1F601}", // Multiple emoji
651        ];
652
653        for s in unicode_cases {
654            let obj = ThinStr::try_allocate_for(s, alloc).unwrap();
655            let uninit = unsafe { &mut *obj.as_ptr() };
656            let thin_str = ThinStr::try_from_str_in(s, uninit).unwrap();
657
658            assert_eq!(thin_str.deref(), s);
659            assert_eq!(thin_str.deref().len(), s.len());
660
661            unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
662        }
663    }
664
665    #[test]
666    fn test_capacity() {
667        // Test that try_from_str_in fails when there's not enough space
668        let test_string = "hello world";
669        let mut small_buffer = [std::mem::MaybeUninit::uninit(); 5]; // Too small
670
671        let result = ThinStr::try_from_str_in(test_string, &mut small_buffer);
672        assert!(result.is_err());
673
674        // Test with exactly the right amount of space
675        let required_size = test_string.len() + core::mem::size_of::<usize>();
676        let mut buffer = vec![std::mem::MaybeUninit::uninit(); required_size];
677
678        let thin_str = ThinStr::try_from_str_in(test_string, &mut buffer).unwrap();
679        assert_eq!(thin_str.deref(), test_string);
680    }
681
682    proptest::proptest! {
683        #![proptest_config(proptest::prelude::ProptestConfig {
684            // Reduce test cases under miri for faster execution
685            cases: if cfg!(miri) { 16 } else { 256 },
686            ..proptest::prelude::ProptestConfig::default()
687        })]
688
689        #[test]
690        fn test_thin_str_properties(test_string in ".*") {
691            use std::borrow::Borrow;
692            use std::hash::{Hash, Hasher};
693            use std::collections::hash_map::DefaultHasher;
694
695            let alloc = &Global;
696
697            // Test layout calculation property
698            let layout = ThinStr::layout_for(&test_string).unwrap();
699            let min_size = test_string.len() + core::mem::size_of::<usize>();
700            assert!(layout.size() >= min_size);
701            assert!(layout.align() >= 1);
702            assert!(layout.align().is_power_of_two());
703
704            // Create ThinStr
705            let obj = ThinStr::try_allocate_for(&test_string, alloc).unwrap();
706            let uninit = unsafe { &mut *obj.as_ptr() };
707            let thin_str = ThinStr::try_from_str_in(&test_string, uninit).unwrap();
708
709            // Test borrowing properties
710            let borrowed_str: &str = thin_str.borrow();
711            assert_eq!(borrowed_str, test_string);
712
713            let borrowed_inline: &InlineString = thin_str.borrow();
714            assert_eq!(borrowed_inline.deref(), test_string);
715
716            // Test deref consistency
717            assert_eq!(thin_str.deref(), test_string);
718            assert_eq!(thin_str.deref().len(), test_string.len());
719
720            // Test hash consistency property
721            let mut hasher1 = DefaultHasher::new();
722            thin_str.hash(&mut hasher1);
723            let hash1 = hasher1.finish();
724
725            let mut hasher2 = DefaultHasher::new();
726            test_string.hash(&mut hasher2);
727            let hash2 = hasher2.finish();
728
729            assert_eq!(hash1, hash2);
730
731            // Test equality property - create another ThinStr with same content
732            let obj2 = ThinStr::try_allocate_for(&test_string, alloc).unwrap();
733            let uninit2 = unsafe { &mut *obj2.as_ptr() };
734            let thin_str2 = ThinStr::try_from_str_in(&test_string, uninit2).unwrap();
735
736            // Should be equal even though they're different allocations
737            assert_eq!(thin_str, thin_str2);
738
739            // Cleanup
740            unsafe {
741                alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout());
742                alloc.deallocate(thin_str2.inner.thin_ptr.size_ptr, thin_str2.layout());
743            }
744        }
745    }
746
747    #[test]
748    fn test_large_string() {
749        let alloc = &Global;
750
751        // Test a reasonably large string
752        let large_string = "x".repeat(10000);
753        let obj = ThinStr::try_allocate_for(&large_string, alloc).unwrap();
754        let uninit = unsafe { &mut *obj.as_ptr() };
755        let thin_str = ThinStr::try_from_str_in(&large_string, uninit).unwrap();
756
757        assert_eq!(thin_str.deref(), large_string);
758        assert_eq!(thin_str.deref().len(), 10000);
759
760        unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
761    }
762}