#![cfg_attr(
any(feature = "strict-provenance", miri),
feature(strict_provenance, ptr_metadata)
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(missing_docs)]
#![cfg_attr(not(test), no_std)]
#![cfg_attr(
doc,
doc = document_features::document_features!(
feature_label = r##"<a class="stab portability" id="feature-{feature}" href="#feature-{feature}"><code>{feature}</code></a>"##
),
)]
use core::{
alloc::Layout,
cell::UnsafeCell,
mem::{forget, transmute_copy, ManuallyDrop, MaybeUninit},
ptr,
};
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{
alloc::{alloc, handle_alloc_error},
boxed::Box,
};
#[cfg_attr(not(doc), repr(C, align(8)))]
pub struct BigBox<T: ?Sized, const N: usize, const A: usize = 8> {
inline: UnsafeCell<[MaybeUninit<u8>; N]>,
boxed: *mut T,
}
unsafe impl<T: Send + ?Sized, const N: usize, const A: usize> Send for BigBox<T, N, A> {}
unsafe impl<T: Sync + ?Sized, const N: usize, const A: usize> Sync for BigBox<T, N, A> {}
fn copy_metadata<T: ?Sized>(meta_ptr: *const T, data_ptr: *const u8) -> *const T {
#[cfg(not(any(feature = "strict-provenance", miri)))]
return meta_ptr
.wrapping_byte_sub(meta_ptr as *const u8 as usize)
.wrapping_byte_add(data_ptr as usize);
#[cfg(any(feature = "strict-provenance", miri))]
return ptr::from_raw_parts(data_ptr, ptr::metadata(meta_ptr));
}
#[cfg(feature = "alloc")]
fn take_into_box<T: ?Sized, const N: usize, const A: usize>(
mut value: InlineBox<T, N, A>,
) -> Box<T> {
let layout = Layout::for_value::<T>(value.as_ref());
let alloc: *mut u8 = if layout.size() == 0 {
#[cfg(not(any(feature = "strict-provenance", miri)))]
unsafe {
core::mem::transmute(layout.align())
}
#[cfg(any(feature = "strict-provenance", miri))]
ptr::without_provenance_mut(layout.align())
} else {
let alloc = unsafe { alloc(layout) };
if alloc.is_null() {
handle_alloc_error(layout);
}
alloc
};
let ptr = ptr::from_mut::<T>(value.as_mut());
unsafe { alloc.copy_from_nonoverlapping(ptr.cast(), layout.size()) };
let alloc = copy_metadata(ptr, alloc).cast_mut();
unsafe { Box::from_raw(alloc) }
}
pub unsafe trait FromSized<T> {
fn fatten_pointer(thin: *const T) -> *const Self;
}
#[macro_export]
macro_rules! impl_from_sized_for_trait_object {
(dyn $trait:path) => {
unsafe impl<'a, T: $trait + 'a> $crate::FromSized<T> for dyn $trait + 'a {
fn fatten_pointer(thin: *const T) -> *const Self {
thin as *const Self
}
}
};
}
impl_from_sized_for_trait_object!(dyn core::any::Any);
unsafe impl<T> FromSized<T> for T {
fn fatten_pointer(thin: *const T) -> *const T {
thin
}
}
unsafe impl<T, const N: usize> FromSized<[T; N]> for [T] {
fn fatten_pointer(thin: *const [T; N]) -> *const Self {
thin as *const Self
}
}
#[cfg_attr(
feature = "alloc",
doc = "This is identical to a [`BigBox`] which always stores its contents on the stack."
)]
#[cfg_attr(
not(feature = "alloc"),
doc = "This is similar to a `Box`, but is backed by a buffer with a static size and exclusively stores its values inline."
)]
#[cfg_attr(not(doc), repr(transparent))]
pub struct InlineBox<T: ?Sized, const N: usize, const A: usize> {
inner: ManuallyDrop<BigBox<T, N, A>>,
}
impl<T: ?Sized, const N: usize, const A: usize> InlineBox<T, N, A> {
unsafe fn from_big_box(big_box: BigBox<T, N, A>) -> Self {
debug_assert!(big_box.is_inline());
Self {
inner: ManuallyDrop::new(big_box),
}
}
pub fn try_new<U>(v: U) -> Result<Self, U>
where
T: FromSized<U>,
{
unsafe {
Self::try_copy_raw(
Layout::new::<U>(),
T::fatten_pointer(ptr::null()),
v,
|v, dst| dst.cast::<U>().write(v),
)
}
}
unsafe fn try_copy_raw<U>(
layout: Layout,
meta: *const T,
cx: U,
writer: impl FnOnce(U, *mut u8),
) -> Result<Self, U> {
if layout.size() > N || layout.align() > 8 {
return Err(cx);
}
let boxed = copy_metadata(meta, ptr::null()).cast_mut();
let mut inst = BigBox::uninit_from_boxed(boxed);
let dst: *mut u8 = inst.inline.get_mut().as_mut_ptr().cast();
writer(cx, dst);
Ok(unsafe { Self::from_big_box(inst) })
}
pub unsafe fn into_unchecked<U>(mut self) -> U
where
T: FromSized<U>,
{
let read = unsafe { ptr::read(self.inner.inline_ptr_mut().cast()) };
forget(self);
read
}
}
#[derive(Debug)]
pub struct TooLarge {
_marker: (),
}
impl<T: Copy, const N: usize, const A: usize> TryFrom<&[T]> for InlineBox<[T], N, A> {
type Error = TooLarge;
fn try_from(v: &[T]) -> Result<Self, TooLarge> {
fn copy_slice<T>(v: &[T], dst: *mut u8) {
unsafe { ptr::copy_nonoverlapping(v.as_ptr(), dst.cast(), v.len()) }
}
let attempt = unsafe { Self::try_copy_raw(Layout::for_value(v), v, v, copy_slice) };
attempt.map_err(|_| TooLarge { _marker: () })
}
}
impl<const N: usize, const A: usize> TryFrom<&str> for InlineBox<str, N, A> {
type Error = TooLarge;
fn try_from(v: &str) -> Result<Self, TooLarge> {
<InlineBox<[u8], N, A>>::try_from(v.as_bytes()).map(|boxed| {
unsafe { transmute_copy(&ManuallyDrop::new(boxed)) }
})
}
}
impl<T, const N: usize, const A: usize> InlineBox<T, N, A> {
pub fn into_inner(self) -> T {
unsafe { self.into_unchecked::<T>() }
}
}
impl<T: ?Sized, const N: usize, const A: usize> AsRef<T> for InlineBox<T, N, A> {
fn as_ref(&self) -> &T {
unsafe { self.inner.inline_ptr().as_ref().unwrap_unchecked() }
}
}
impl<T: ?Sized, const N: usize, const A: usize> AsMut<T> for InlineBox<T, N, A> {
fn as_mut(&mut self) -> &mut T {
unsafe { self.inner.inline_ptr_mut().as_mut().unwrap_unchecked() }
}
}
impl<T: ?Sized, const N: usize, const A: usize> Drop for InlineBox<T, N, A> {
fn drop(&mut self) {
unsafe { self.inner.inline_ptr_mut().drop_in_place() };
}
}
impl<T: ?Sized, const N: usize, const A: usize> From<InlineBox<T, N, A>> for BigBox<T, N, A> {
fn from(mut value: InlineBox<T, N, A>) -> Self {
let big_box = unsafe { ManuallyDrop::take(&mut value.inner) };
forget(value);
big_box
}
}
#[cfg(feature = "alloc")]
impl<T: ?Sized, const N: usize, const A: usize> From<&T> for BigBox<T, N, A>
where
for<'a> &'a T: TryInto<InlineBox<T, N, A>, Error = TooLarge> + Into<Box<T>>,
{
fn from(v: &T) -> Self {
v.try_into()
.map_or_else(|_| BigBox::new_boxed(v.into()), BigBox::from)
}
}
#[cfg(feature = "alloc")]
#[allow(missing_docs)]
pub enum BoxCapture<T: ?Sized, const N: usize, const A: usize> {
Boxed(Box<T>),
Inline(InlineBox<T, N, A>),
}
impl<T: ?Sized, const N: usize, const A: usize> BigBox<T, N, A> {
fn uninit_from_boxed(boxed: *mut T) -> Self {
Self {
inline: UnsafeCell::new([MaybeUninit::uninit(); N]),
boxed,
}
}
pub fn is_inline(&self) -> bool {
self.boxed.is_null()
}
fn inline_ptr(&self) -> *const T {
let data_start = self.inline.get();
copy_metadata(self.boxed, data_start.cast())
}
fn inline_ptr_mut(&mut self) -> *mut T {
let data_start = self.inline.get_mut().as_mut_ptr();
copy_metadata(self.boxed, data_start.cast()).cast_mut()
}
}
#[cfg(feature = "alloc")]
impl<T: ?Sized, const N: usize, const A: usize> BigBox<T, N, A> {
#[inline]
pub fn new_boxed(boxed: Box<T>) -> Self {
Self::uninit_from_boxed(Box::into_raw(boxed))
}
pub fn new<U>(v: U) -> Self
where
T: FromSized<U>,
{
match InlineBox::try_new(v) {
Ok(inline) => inline.into(),
Err(v) => {
let raw: *mut U = Box::into_raw(Box::new(v));
let fat: *mut T = T::fatten_pointer(raw).cast_mut();
Self::uninit_from_boxed(fat)
}
}
}
pub fn into_boxed(self) -> Box<T> {
match self.take() {
BoxCapture::Boxed(boxed) => boxed,
BoxCapture::Inline(inline) => take_into_box(inline),
}
}
pub fn take(self) -> BoxCapture<T, N, A> {
let capture = if self.is_inline() {
let inline = unsafe { InlineBox::from_big_box(self) };
BoxCapture::Inline(inline)
} else {
let boxed = unsafe { Box::from_raw(self.boxed) };
forget(self);
BoxCapture::Boxed(boxed)
};
capture
}
}
impl<T: ?Sized, const N: usize, const A: usize> AsRef<T> for BigBox<T, N, A> {
fn as_ref(&self) -> &T {
let data_ptr = self
.is_inline()
.then(|| self.inline_ptr())
.unwrap_or(self.boxed);
unsafe { data_ptr.as_ref().unwrap_unchecked() }
}
}
impl<T: ?Sized, const N: usize, const A: usize> AsMut<T> for BigBox<T, N, A> {
fn as_mut(&mut self) -> &mut T {
let data_ptr = self
.is_inline()
.then(|| self.inline_ptr_mut())
.unwrap_or(self.boxed);
unsafe { data_ptr.as_mut().unwrap_unchecked() }
}
}
impl<T: ?Sized, const N: usize, const A: usize> Drop for BigBox<T, N, A> {
fn drop(&mut self) {
if self.is_inline() {
unsafe { self.inline_ptr_mut().drop_in_place() }
} else {
#[cfg(feature = "alloc")]
unsafe {
drop(Box::from_raw(self.boxed))
}
}
}
}
#[cfg(test)]
mod tests {
use core::fmt::Debug;
use std::{
any::{Any, TypeId},
array::from_fn,
cell::Cell,
marker::PhantomData,
sync::{Arc, Mutex},
thread::JoinHandle,
};
use crate::*;
trait Anything: 'static + Debug {
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
fn typeid(&self) -> TypeId;
}
impl_from_sized_for_trait_object!(dyn Anything);
impl<T: 'static + Debug> Anything for T {
fn as_any(&self) -> &dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
fn typeid(&self) -> TypeId {
TypeId::of::<Self>()
}
}
trait AnySync: Anything + Send + Sync {}
impl<T: Anything + Send + Sync> AnySync for T {}
impl_from_sized_for_trait_object!(dyn AnySync);
fn assert_roundtrip<T: Anything + PartialEq + Debug + Clone>(v: T) {
let dv = BigBox::<dyn Anything, 64, 8>::new(v.clone());
let v2: &T = dv.as_ref().as_any().downcast_ref().unwrap();
assert_eq!(v2, &v);
let boxed = dv.into_boxed();
println!("{boxed:?} vs {}", std::any::type_name::<T>());
println!("{:?} vs {:?}", boxed.typeid(), TypeId::of::<T>());
let v2: T = *boxed.into_any().downcast().unwrap();
assert_eq!(v2, v);
}
#[test]
fn roundtrip() {
assert_roundtrip(());
assert_roundtrip(PhantomData::<&()>);
assert_roundtrip(u8::MIN);
assert_roundtrip(u8::MAX);
assert_roundtrip(usize::MIN);
assert_roundtrip(usize::MAX);
assert_roundtrip(u128::MIN);
assert_roundtrip(u128::MAX);
assert_roundtrip([110_u8; 1000]);
}
#[test]
fn cell() {
let boxed: BigBox<dyn Anything, 64> = BigBox::new(Cell::new(5_u8));
let a: &Cell<u8> = boxed.as_ref().as_any().downcast_ref().unwrap();
let b: &Cell<u8> = boxed.as_ref().as_any().downcast_ref().unwrap();
a.set(10);
assert_eq!(a.get(), 10);
assert_eq!(b.get(), 10);
b.set(12);
assert_eq!(a.get(), 12);
assert_eq!(b.get(), 12);
}
#[test]
fn mutex() {
let mtx = Mutex::new(10_usize);
let boxed: BigBox<dyn AnySync, 64> = BigBox::new(mtx);
assert!(boxed.is_inline());
let arc = Arc::new(boxed);
let threads: [JoinHandle<()>; 10] = from_fn(|i| {
let arc = arc.clone();
std::thread::spawn(move || {
let mut num = arc
.as_ref()
.as_ref()
.as_any()
.downcast_ref::<Mutex<usize>>()
.unwrap()
.lock()
.unwrap();
*num += i;
})
});
threads.into_iter().for_each(|td| td.join().unwrap());
let boxed = Arc::into_inner(arc).unwrap();
assert_eq!(
*boxed
.into_boxed()
.into_any()
.downcast::<Mutex<usize>>()
.unwrap()
.get_mut()
.unwrap(),
55,
);
}
#[test]
fn conversions() {
type B<T> = BigBox<T, 32>;
let a = "the pink champagne on ice.";
let b = "all the leaves are brown and the sky is gray.";
let a_boxed: B<str> = B::from(a);
let b_boxed: B<str> = B::from(b);
assert!(a_boxed.is_inline() && !b_boxed.is_inline());
assert_eq!(a_boxed.as_ref(), a);
assert_eq!(b_boxed.as_ref(), b);
let a: &[u32] = &[150u32; 2];
let b: &[u32] = &[159u32; 40];
let a_boxed: B<[u32]> = B::from(a);
let b_boxed: B<[u32]> = B::from(b);
assert!(a_boxed.is_inline() && !b_boxed.is_inline());
assert_eq!(a_boxed.as_ref(), a);
assert_eq!(b_boxed.as_ref(), b);
}
}