use core::{
alloc::Layout,
fmt,
marker::PhantomData,
mem::size_of,
ops::{Deref, DerefMut},
ptr::NonNull,
};
use ptr_meta::Pointee;
use crate::alloc::alloc::{alloc, dealloc};
pub struct ThinBox<T: Pointee + ?Sized> {
ptr: NonNull<()>,
_phantom: PhantomData<T>,
}
unsafe impl<T: Pointee + Send + ?Sized> Send for ThinBox<T> {}
unsafe impl<T: Pointee + Sync + ?Sized> Sync for ThinBox<T> {}
impl<T: Pointee + ?Sized> Drop for ThinBox<T> {
fn drop(&mut self) {
let ptr = self.as_ptr();
let value = unsafe { &*ptr };
let value_layout = Layout::for_value(value);
unsafe {
self.as_ptr().drop_in_place();
}
let (layout, header) = Self::layout_for(value_layout);
if layout.size() > 0 {
unsafe {
dealloc(ptr.cast::<u8>().sub(header), layout);
}
}
}
}
impl<T: fmt::Debug + Pointee + ?Sized> fmt::Debug for ThinBox<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deref().fmt(f)
}
}
impl<T: fmt::Display + Pointee + ?Sized> fmt::Display for ThinBox<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deref().fmt(f)
}
}
impl<T: Pointee + ?Sized> ThinBox<T> {
fn layout_for(value_layout: Layout) -> (Layout, usize) {
let meta_layout = Layout::new::<T::Metadata>();
if meta_layout.size() == 0 {
(value_layout, 0)
} else {
let align = usize::max(value_layout.align(), meta_layout.align());
let header = usize::max(align, meta_layout.size());
let size = value_layout.size() + header;
let layout = Layout::from_size_align(size, align).unwrap();
(layout, header)
}
}
pub unsafe fn new_unchecked<U, F>(value: U, cast: F) -> Self
where
F: FnOnce(*mut U) -> *mut T,
{
let (layout, header) = Self::layout_for(Layout::new::<U>());
if layout.size() == 0 {
Self {
ptr: NonNull::dangling(),
_phantom: PhantomData,
}
} else {
let raw_ptr = unsafe { NonNull::new(alloc(layout)).unwrap() };
let value_ptr = unsafe { raw_ptr.as_ptr().add(header).cast::<U>() };
unsafe {
value_ptr.write(value);
}
let ptr = cast(value_ptr);
let meta_ptr = unsafe {
raw_ptr
.as_ptr()
.add(header - size_of::<T::Metadata>())
.cast::<T::Metadata>()
};
unsafe {
meta_ptr.write(ptr_meta::metadata(ptr));
}
Self {
ptr: unsafe { NonNull::new_unchecked(ptr.cast()) },
_phantom: PhantomData,
}
}
}
pub fn as_ptr(&self) -> *mut T {
let data_address = self.ptr.as_ptr();
let metadata = unsafe { *data_address.cast::<T::Metadata>().sub(1) };
ptr_meta::from_raw_parts_mut(data_address, metadata)
}
}
impl<T: Pointee + ?Sized> Deref for ThinBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.as_ptr().cast_const() }
}
}
impl<T: Pointee + ?Sized> DerefMut for ThinBox<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.as_ptr() }
}
}
#[cfg(test)]
mod tests {
use crate::{
alloc::string::{String, ToString},
thin_box::ThinBox,
};
#[ptr_meta::pointee]
trait DynTrait {
fn int(&self) -> i32;
}
impl DynTrait for () {
fn int(&self) -> i32 {
10
}
}
impl DynTrait for i32 {
fn int(&self) -> i32 {
*self
}
}
impl DynTrait for String {
fn int(&self) -> i32 {
self.parse().unwrap()
}
}
#[test]
fn sized_types() {
let box_unit = unsafe { ThinBox::new_unchecked((), |x| x) };
assert_eq!(*box_unit, ());
let box_int = unsafe { ThinBox::new_unchecked(10, |x| x) };
assert_eq!(*box_int, 10);
let box_string =
unsafe { ThinBox::new_unchecked("hello world".to_string(), |x| x) };
assert_eq!(*box_string, "hello world");
}
#[test]
fn unsized_types() {
let box_dyn_int =
unsafe { ThinBox::new_unchecked(10, |x| x as *mut dyn DynTrait) };
assert_eq!(box_dyn_int.int(), 10);
let box_dyn_string = unsafe {
ThinBox::new_unchecked("10".to_string(), |x| x as *mut dyn DynTrait)
};
assert_eq!(box_dyn_string.int(), 10);
let box_slice = unsafe {
ThinBox::new_unchecked([1, 2, 3, 4], |x| x as *mut [i32])
};
assert_eq!(*box_slice, [1, 2, 3, 4]);
}
#[test]
fn zst_dst() {
let box_unit_debug =
unsafe { ThinBox::new_unchecked((), |x| x as *mut dyn DynTrait) };
assert_eq!(box_unit_debug.int(), 10);
let box_empty_slice =
unsafe { ThinBox::new_unchecked([], |x| x as *mut [i32]) };
assert_eq!(*box_empty_slice, []);
}
}