1use 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#[derive(Copy, Clone)]
19#[repr(C)]
20pub struct ThinSlice<'a, T: Copy> {
21 thin_ptr: ThinPtr<T>,
22
23 _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#[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 pub unsafe fn from_raw(this: NonNull<c_void>) -> Self {
55 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
77unsafe 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 unsafe { &*thin_ptr.inline_string_ptr().as_ptr() }
152 }
153}
154
155#[repr(transparent)]
156#[derive(Clone, Copy)]
157struct ThinPtr<T: Copy> {
158 size_ptr: NonNull<u8>,
160 _marker: PhantomData<T>,
161}
162
163#[repr(C)]
164pub struct InlineSlice<T: Copy> {
165 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 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 const fn len(self) -> usize {
194 let size = unsafe { self.size_ptr.cast::<[u8; USIZE_WIDTH]>().as_ptr().read() };
196 usize::from_ne_bytes(size)
197 }
198
199 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 unsafe { NonNull::new_unchecked(slice as *mut [()] as *mut InlineSlice<T>) }
206 }
207}
208
209impl ThinPtr<u8> {
210 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 unsafe { NonNull::new_unchecked(slice as *mut [()] as *mut InlineString) }
221 }
222}
223
224impl<'a, T: Copy> ThinSlice<'a, T> {
226 pub fn len(&self) -> usize {
228 self.thin_ptr.len()
229 }
230
231 pub fn is_empty(&self) -> bool {
233 self.len() == 0
234 }
235
236 pub fn as_slice(&self) -> &[T] {
238 let inline_slice = unsafe { self.thin_ptr.inline_slice_ptr().as_ref() };
241 &inline_slice.data
242 }
243
244 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 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 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 let size_bytes = slice.len().to_ne_bytes();
282 unsafe { core::ptr::copy_nonoverlapping(size_bytes.as_ptr(), allocation, USIZE_WIDTH) };
284
285 let data = unsafe { allocation.add(USIZE_WIDTH).cast::<T>() };
287 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 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 let size_bytes = slice.len().to_ne_bytes();
314 core::ptr::copy_nonoverlapping(size_bytes.as_ptr(), allocation, USIZE_WIDTH);
315
316 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 pub fn layout(&self) -> Layout {
331 Self::layout_for(self.as_slice()).unwrap_or_else(|_| unsafe {
335 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 unsafe { self.thin_ptr.inline_slice_ptr().as_ref() }
379 }
380}
381
382impl<'a> ThinStr<'a> {
384 pub fn layout_for(str: &str) -> Result<Layout, AllocError> {
388 ThinSlice::layout_for(str.as_bytes())
389 }
390
391 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 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 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 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 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 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#[repr(C)]
486pub struct ConstString<const N: usize> {
487 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 #[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 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 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 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 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 "é", "€", "🦀", "\u{0000}", "\u{FFFD}", "a\u{0000}b", "\n\r\t", "\u{1F600}\u{1F601}", ];
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 let test_string = "hello world";
669 let mut small_buffer = [std::mem::MaybeUninit::uninit(); 5]; let result = ThinStr::try_from_str_in(test_string, &mut small_buffer);
672 assert!(result.is_err());
673
674 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 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 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 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 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 assert_eq!(thin_str.deref(), test_string);
718 assert_eq!(thin_str.deref().len(), test_string.len());
719
720 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 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 assert_eq!(thin_str, thin_str2);
738
739 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 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}