use std::alloc::{Layout, alloc, dealloc, handle_alloc_error};
use std::ffi::CStr;
use std::fmt;
use std::ptr::{self, NonNull};
use std::sync::atomic::AtomicPtr;
use crate::atomic;
enum ThinStrRaw {}
pub struct ThinStr(NonNull<ThinStrRaw>);
pub struct AtomicThinStr(AtomicPtr<ThinStrRaw>);
impl ThinStr {
fn layout(size: usize) -> (Layout, usize) {
Layout::array::<u8>(size)
.and_then(|layout| Layout::new::<usize>().extend(layout))
.unwrap_or_else(|_| handle_alloc_error(Layout::new::<()>()))
}
pub fn new(src: &(impl ?Sized + AsRef<str>)) -> Self {
let slice = src.as_ref().as_bytes();
let (layout, offset) = Self::layout(slice.len());
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
handle_alloc_error(layout);
}
unsafe { ptr.cast::<usize>().write(slice.len()) };
unsafe {
ptr.byte_add(offset).copy_from_nonoverlapping(slice.as_ptr(), slice.len());
};
Self(unsafe { NonNull::new_unchecked(ptr.cast()) })
}
#[inline]
pub fn from_cstr(cstr: &(impl ?Sized + AsRef<CStr>)) -> Option<Self> {
cstr.as_ref().to_str().ok().map(Self::new)
}
#[inline]
pub fn len(&self) -> usize {
unsafe { self.0.cast::<usize>().read() }
}
#[inline]
pub fn as_ptr(&self) -> NonNull<[u8]> {
let size = self.len();
unsafe {
NonNull::slice_from_raw_parts(self.0.byte_add(Self::layout(size).1).cast(), size)
}
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
unsafe { self.as_ptr().as_ref() }
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
}
#[inline]
fn into_raw(self) -> *mut ThinStrRaw {
let ptr = self.0.as_ptr().cast();
std::mem::forget(self);
ptr
}
#[inline]
unsafe fn from_raw(raw: *mut ThinStrRaw) -> Option<Self> {
NonNull::new(raw.cast()).map(Self)
}
}
impl Drop for ThinStr {
fn drop(&mut self) {
unsafe { dealloc(self.0.as_ptr().cast(), Self::layout(self.len()).0); }
}
}
impl fmt::Debug for ThinStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_str().fmt(f) }
}
impl AsRef<str> for ThinStr {
#[inline]
fn as_ref(&self) -> &str { self.as_str() }
}
impl AtomicThinStr {
pub const fn new() -> Self {
Self(AtomicPtr::new(ptr::null_mut()))
}
pub fn load(&self) -> Option<ThinStr> {
unsafe { ThinStr::from_raw(atomic::load!(self.0)) }
}
pub fn store(&self, thin: ThinStr) {
atomic::store!(self.0, thin.into_raw());
}
}
#[cfg(test)]
mod tests {
use super::ThinStr;
#[test]
fn eq() {
let wide = "foo";
let thin = ThinStr::new(wide);
assert_eq!(wide.len(), thin.len());
assert_eq!(wide, thin.as_str());
}
#[test]
fn restore() {
let wide = "bar";
let thin = unsafe {
ThinStr::from_raw(ThinStr::new(wide).into_raw()).unwrap()
};
assert_eq!(wide, thin.as_str());
}
}