use libdd_alloc::{AllocError, Allocator};
use std::alloc::Layout;
use std::borrow::Borrow;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::ptr::NonNull;
use std::{fmt, hash, ptr};
const USIZE_WIDTH: usize = core::mem::size_of::<usize>();
#[derive(Copy, Clone)]
#[repr(C)]
pub struct ThinSlice<'a, T: Copy> {
thin_ptr: ThinPtr<T>,
_marker: PhantomData<&'a [T]>,
}
impl<T: Copy + fmt::Debug> fmt::Debug for ThinSlice<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deref().fmt(f)
}
}
#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct ThinStr<'a> {
inner: ThinSlice<'a, u8>,
}
impl ThinStr<'_> {
pub fn into_raw(self) -> NonNull<c_void> {
self.inner.thin_ptr.size_ptr.cast()
}
pub unsafe fn from_raw(this: NonNull<c_void>) -> Self {
let thin_ptr = ThinPtr {
size_ptr: this.cast(),
_marker: PhantomData,
};
Self {
inner: ThinSlice {
thin_ptr,
_marker: PhantomData,
},
}
}
}
impl fmt::Debug for ThinStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deref().fmt(f)
}
}
unsafe impl<T: Copy> Send for ThinPtr<T> {}
unsafe impl<T: Copy> Sync for ThinPtr<T> {}
unsafe impl<T: Copy> Send for ThinSlice<'_, T> {}
unsafe impl<T: Copy> Sync for ThinSlice<'_, T> {}
unsafe impl Send for ThinStr<'_> {}
unsafe impl Sync for ThinStr<'_> {}
impl ThinStr<'static> {
pub const fn new() -> ThinStr<'static> {
ThinStr {
inner: ThinSlice {
thin_ptr: EMPTY_INLINE_STRING.as_thin_ptr(),
_marker: PhantomData,
},
}
}
pub const fn end_timestamp_ns() -> ThinStr<'static> {
ThinStr {
inner: ThinSlice {
thin_ptr: END_TIMESTAMP_NS.as_thin_ptr(),
_marker: PhantomData,
},
}
}
pub const fn local_root_span_id() -> ThinStr<'static> {
ThinStr {
inner: ThinSlice {
thin_ptr: LOCAL_ROOT_SPAN_ID.as_thin_ptr(),
_marker: PhantomData,
},
}
}
pub const fn trace_endpoint() -> ThinStr<'static> {
ThinStr {
inner: ThinSlice {
thin_ptr: TRACE_ENDPOINT.as_thin_ptr(),
_marker: PhantomData,
},
}
}
pub const fn span_id() -> ThinStr<'static> {
ThinStr {
inner: ThinSlice {
thin_ptr: SPAN_ID.as_thin_ptr(),
_marker: PhantomData,
},
}
}
}
impl Default for ThinStr<'static> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> Borrow<InlineString> for ConstString<N> {
fn borrow(&self) -> &InlineString {
let thin_ptr = ThinPtr {
size_ptr: NonNull::from(self).cast::<u8>(),
_marker: PhantomData,
};
unsafe { &*thin_ptr.inline_string_ptr().as_ptr() }
}
}
#[repr(transparent)]
#[derive(Clone, Copy)]
struct ThinPtr<T: Copy> {
size_ptr: NonNull<u8>,
_marker: PhantomData<T>,
}
#[repr(C)]
pub struct InlineSlice<T: Copy> {
size: [u8; core::mem::size_of::<usize>()],
data: [T],
}
impl<T: Copy> Deref for InlineSlice<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
&self.data
}
}
#[repr(C)]
pub struct InlineString {
size: [u8; core::mem::size_of::<usize>()],
data: str,
}
impl Deref for InlineString {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T: Copy> ThinPtr<T> {
const fn len(self) -> usize {
let size = unsafe { self.size_ptr.cast::<[u8; USIZE_WIDTH]>().as_ptr().read() };
usize::from_ne_bytes(size)
}
const fn inline_slice_ptr(self) -> NonNull<InlineSlice<T>> {
let len = self.len();
let slice = ptr::slice_from_raw_parts_mut(self.size_ptr.as_ptr(), len);
unsafe { NonNull::new_unchecked(slice as *mut [()] as *mut InlineSlice<T>) }
}
}
impl ThinPtr<u8> {
const unsafe fn inline_string_ptr(self) -> NonNull<InlineString> {
let len = self.len();
let slice = ptr::slice_from_raw_parts_mut(self.size_ptr.as_ptr(), len);
unsafe { NonNull::new_unchecked(slice as *mut [()] as *mut InlineString) }
}
}
impl<'a, T: Copy> ThinSlice<'a, T> {
pub fn len(&self) -> usize {
self.thin_ptr.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_slice(&self) -> &[T] {
let inline_slice = unsafe { self.thin_ptr.inline_slice_ptr().as_ref() };
&inline_slice.data
}
pub fn layout_for(slice: &[T]) -> Result<Layout, AllocError> {
let len = slice.len();
let element_size = core::mem::size_of::<T>();
let data_size = len.checked_mul(element_size).ok_or(AllocError)?;
let total_size = USIZE_WIDTH.checked_add(data_size).ok_or(AllocError)?;
Layout::from_size_align(total_size, 1).map_err(|_| AllocError)
}
pub fn try_allocate_for<A: Allocator>(
slice: &[T],
alloc: &A,
) -> Result<NonNull<[MaybeUninit<u8>]>, AllocError> {
let layout = Self::layout_for(slice)?;
let obj = alloc.allocate(layout)?;
let ptr = obj.cast::<MaybeUninit<u8>>();
Ok(NonNull::slice_from_raw_parts(ptr, obj.len()))
}
pub fn try_from_slice_in(
slice: &[T],
spare_capacity: &'a mut [MaybeUninit<u8>],
) -> Result<Self, AllocError> {
let layout = Self::layout_for(slice)?;
if spare_capacity.len() < layout.size() {
return Err(AllocError);
}
let allocation = spare_capacity.as_mut_ptr().cast::<u8>();
let size_bytes = slice.len().to_ne_bytes();
unsafe { core::ptr::copy_nonoverlapping(size_bytes.as_ptr(), allocation, USIZE_WIDTH) };
let data = unsafe { allocation.add(USIZE_WIDTH).cast::<T>() };
unsafe { core::ptr::copy_nonoverlapping(slice.as_ptr(), data, slice.len()) };
let size_ptr = unsafe { NonNull::new_unchecked(allocation) };
let thin_ptr = ThinPtr {
size_ptr,
_marker: PhantomData,
};
let _marker = PhantomData;
Ok(ThinSlice { thin_ptr, _marker })
}
pub unsafe fn from_slice_in_unchecked(
slice: &[T],
spare_capacity: &'a mut [MaybeUninit<u8>],
) -> Self {
let allocation = spare_capacity.as_mut_ptr().cast::<u8>();
let size_bytes = slice.len().to_ne_bytes();
core::ptr::copy_nonoverlapping(size_bytes.as_ptr(), allocation, USIZE_WIDTH);
let data = unsafe { allocation.add(USIZE_WIDTH).cast::<T>() };
core::ptr::copy_nonoverlapping(slice.as_ptr(), data, slice.len());
let size_ptr = NonNull::new_unchecked(allocation);
let thin_ptr = ThinPtr {
size_ptr,
_marker: PhantomData,
};
let _marker = PhantomData;
ThinSlice { thin_ptr, _marker }
}
pub fn layout(&self) -> Layout {
Self::layout_for(self.as_slice()).unwrap_or_else(|_| unsafe {
Layout::from_size_align_unchecked(USIZE_WIDTH + self.len(), 1)
})
}
}
impl<T: Copy> Deref for ThinSlice<'_, T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T: Copy> PartialEq for ThinSlice<'_, T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: Copy> Eq for ThinSlice<'_, T> where T: Eq {}
impl<T: Copy> hash::Hash for ThinSlice<'_, T>
where
T: hash::Hash,
{
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.as_slice().hash(state)
}
}
impl<T: Copy> Borrow<[T]> for ThinSlice<'_, T> {
fn borrow(&self) -> &[T] {
self.as_slice()
}
}
impl<T: Copy> Borrow<InlineSlice<T>> for ThinSlice<'_, T> {
fn borrow(&self) -> &InlineSlice<T> {
unsafe { self.thin_ptr.inline_slice_ptr().as_ref() }
}
}
impl<'a> ThinStr<'a> {
pub fn layout_for(str: &str) -> Result<Layout, AllocError> {
ThinSlice::layout_for(str.as_bytes())
}
pub fn try_allocate_for<A: Allocator>(
str: &str,
alloc: &A,
) -> Result<NonNull<[MaybeUninit<u8>]>, AllocError> {
ThinSlice::try_allocate_for(str.as_bytes(), alloc)
}
pub fn try_from_str_in(
str: &str,
spare_capacity: &'a mut [MaybeUninit<u8>],
) -> Result<Self, AllocError> {
let inner = ThinSlice::try_from_slice_in(str.as_bytes(), spare_capacity)?;
Ok(ThinStr { inner })
}
pub unsafe fn from_str_in_unchecked(
str: &str,
spare_capacity: &'a mut [MaybeUninit<u8>],
) -> Self {
let inner = ThinSlice::from_slice_in_unchecked(str.as_bytes(), spare_capacity);
ThinStr { inner }
}
pub fn layout(&self) -> Layout {
self.inner.layout()
}
}
impl Deref for ThinStr<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
let inline_string: &InlineString = self.borrow();
&inline_string.data
}
}
impl Borrow<str> for ThinStr<'_> {
fn borrow(&self) -> &str {
self.deref()
}
}
impl Borrow<InlineString> for ThinStr<'_> {
fn borrow(&self) -> &InlineString {
unsafe { self.inner.thin_ptr.inline_string_ptr().as_ref() }
}
}
impl PartialEq for ThinStr<'_> {
fn eq(&self, other: &Self) -> bool {
self.deref() == other.deref()
}
}
impl Eq for ThinStr<'_> {}
impl hash::Hash for ThinStr<'_> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.deref().hash(state)
}
}
impl<'a> From<ThinSlice<'a, u8>> for ThinStr<'a> {
fn from(inner: ThinSlice<'a, u8>) -> Self {
ThinStr { inner }
}
}
impl<'a> From<ThinStr<'a>> for ThinSlice<'a, u8> {
fn from(thin_str: ThinStr<'a>) -> Self {
thin_str.inner
}
}
#[repr(C)]
pub struct ConstString<const N: usize> {
size: [u8; core::mem::size_of::<usize>()],
data: [u8; N],
}
impl<const N: usize> ConstString<N> {
const fn new(str: &str) -> Self {
#[allow(clippy::panic)]
if str.len() != N {
panic!("string length and storage mismatch for ConstString")
}
ConstString::<N> {
size: N.to_ne_bytes(),
data: {
let src = str.as_bytes();
let mut dst = [0u8; N];
let mut i = 0usize;
while i < N {
dst[i] = src[i];
i += 1;
}
dst
},
}
}
const fn as_thin_ptr(&self) -> ThinPtr<u8> {
let ptr = core::ptr::addr_of!(self.size).cast::<u8>();
let size_ptr = unsafe { NonNull::new_unchecked(ptr.cast_mut()) };
ThinPtr {
size_ptr,
_marker: PhantomData,
}
}
}
static EMPTY_INLINE_STRING: ConstString<0> = ConstString::new("");
static END_TIMESTAMP_NS: ConstString<16> = ConstString::new("end_timestamp_ns");
static LOCAL_ROOT_SPAN_ID: ConstString<18> = ConstString::new("local root span id");
static TRACE_ENDPOINT: ConstString<14> = ConstString::new("trace endpoint");
static SPAN_ID: ConstString<7> = ConstString::new("span id");
#[cfg(test)]
mod tests {
use super::*;
use libdd_alloc::Global;
const TEST_STRINGS: [&str; 5] = [
"datadog",
"MyNamespace.MyClass.MyMethod(Int32 id, String name)",
"/var/run/datadog/apm.socket",
"[truncated]",
"Sidekiq::❨╯°□°❩╯︵┻━┻",
];
#[test]
fn test_allocation_and_deallocation() {
let alloc = &Global;
let mut thin_strs: Vec<ThinStr> = TEST_STRINGS
.iter()
.copied()
.map(|str| {
let obj = ThinStr::try_allocate_for(str, alloc).unwrap();
let uninit = unsafe { &mut *obj.as_ptr() };
let thin_str = ThinStr::try_from_str_in(str, uninit).unwrap();
let actual = thin_str.deref();
assert_eq!(str, actual);
thin_str
})
.collect();
for (thin_str, str) in thin_strs.iter().zip(TEST_STRINGS) {
let actual = thin_str.deref();
assert_eq!(str, actual);
}
for thin_str in thin_strs.drain(..) {
unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
}
}
#[test]
fn test_empty_string() {
let alloc = &Global;
let obj = ThinStr::try_allocate_for("", alloc).unwrap();
let uninit = unsafe { &mut *obj.as_ptr() };
let thin_str = ThinStr::try_from_str_in("", uninit).unwrap();
assert_eq!(thin_str.deref(), "");
assert_eq!(thin_str.deref().len(), 0);
unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
}
#[test]
fn test_single_byte_strings() {
let alloc = &Global;
let single_bytes = ["a", "z", "0", "9", "!", "~"];
for &s in &single_bytes {
let obj = ThinStr::try_allocate_for(s, alloc).unwrap();
let uninit = unsafe { &mut *obj.as_ptr() };
let thin_str = ThinStr::try_from_str_in(s, uninit).unwrap();
assert_eq!(thin_str.deref(), s);
assert_eq!(thin_str.deref().len(), 1);
unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
}
}
#[test]
fn test_boundary_lengths() {
let alloc = &Global;
let test_cases = [
("", 0),
("a", 1),
("ab", 2),
("abc", 3),
("abcd", 4),
("abcdefg", 7),
("abcdefgh", 8),
("abcdefghijklmno", 15),
("abcdefghijklmnop", 16),
("abcdefghijklmnopqrstuvwxyz123456", 32),
("abcdefghijklmnopqrstuvwxyz1234567", 33),
];
for (s, expected_len) in test_cases {
assert_eq!(s.len(), expected_len);
let obj = ThinStr::try_allocate_for(s, alloc).unwrap();
let uninit = unsafe { &mut *obj.as_ptr() };
let thin_str = ThinStr::try_from_str_in(s, uninit).unwrap();
assert_eq!(thin_str.deref(), s);
assert_eq!(thin_str.deref().len(), expected_len);
unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
}
}
#[test]
fn test_unicode_edge_cases() {
let alloc = &Global;
let unicode_cases = [
"é", "€", "🦀", "\u{0000}", "\u{FFFD}", "a\u{0000}b", "\n\r\t", "\u{1F600}\u{1F601}", ];
for s in unicode_cases {
let obj = ThinStr::try_allocate_for(s, alloc).unwrap();
let uninit = unsafe { &mut *obj.as_ptr() };
let thin_str = ThinStr::try_from_str_in(s, uninit).unwrap();
assert_eq!(thin_str.deref(), s);
assert_eq!(thin_str.deref().len(), s.len());
unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
}
}
#[test]
fn test_capacity() {
let test_string = "hello world";
let mut small_buffer = [std::mem::MaybeUninit::uninit(); 5];
let result = ThinStr::try_from_str_in(test_string, &mut small_buffer);
assert!(result.is_err());
let required_size = test_string.len() + core::mem::size_of::<usize>();
let mut buffer = vec![std::mem::MaybeUninit::uninit(); required_size];
let thin_str = ThinStr::try_from_str_in(test_string, &mut buffer).unwrap();
assert_eq!(thin_str.deref(), test_string);
}
proptest::proptest! {
#![proptest_config(proptest::prelude::ProptestConfig {
// Reduce test cases under miri for faster execution
cases: if cfg!(miri) { 16 } else { 256 },
..proptest::prelude::ProptestConfig::default()
})]
#[test]
fn test_thin_str_properties(test_string in ".*") {
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
let alloc = &Global;
let layout = ThinStr::layout_for(&test_string).unwrap();
let min_size = test_string.len() + core::mem::size_of::<usize>();
assert!(layout.size() >= min_size);
assert!(layout.align() >= 1);
assert!(layout.align().is_power_of_two());
let obj = ThinStr::try_allocate_for(&test_string, alloc).unwrap();
let uninit = unsafe { &mut *obj.as_ptr() };
let thin_str = ThinStr::try_from_str_in(&test_string, uninit).unwrap();
let borrowed_str: &str = thin_str.borrow();
assert_eq!(borrowed_str, test_string);
let borrowed_inline: &InlineString = thin_str.borrow();
assert_eq!(borrowed_inline.deref(), test_string);
assert_eq!(thin_str.deref(), test_string);
assert_eq!(thin_str.deref().len(), test_string.len());
let mut hasher1 = DefaultHasher::new();
thin_str.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
test_string.hash(&mut hasher2);
let hash2 = hasher2.finish();
assert_eq!(hash1, hash2);
let obj2 = ThinStr::try_allocate_for(&test_string, alloc).unwrap();
let uninit2 = unsafe { &mut *obj2.as_ptr() };
let thin_str2 = ThinStr::try_from_str_in(&test_string, uninit2).unwrap();
assert_eq!(thin_str, thin_str2);
unsafe {
alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout());
alloc.deallocate(thin_str2.inner.thin_ptr.size_ptr, thin_str2.layout());
}
}
}
#[test]
fn test_large_string() {
let alloc = &Global;
let large_string = "x".repeat(10000);
let obj = ThinStr::try_allocate_for(&large_string, alloc).unwrap();
let uninit = unsafe { &mut *obj.as_ptr() };
let thin_str = ThinStr::try_from_str_in(&large_string, uninit).unwrap();
assert_eq!(thin_str.deref(), large_string);
assert_eq!(thin_str.deref().len(), 10000);
unsafe { alloc.deallocate(thin_str.inner.thin_ptr.size_ptr, thin_str.layout()) };
}
}