dyn_utils/
storage.rs

1//! The storages backing [`DynObject`](crate::DynObject).
2
3#[cfg(any(feature = "alloc", doc))]
4use alloc::boxed::Box as StdBox;
5use core::{
6    alloc::Layout,
7    hint::unreachable_unchecked,
8    marker::{PhantomData, PhantomPinned},
9    mem::MaybeUninit,
10    pin::Pin,
11    ptr::NonNull,
12};
13
14pub use elain::{Align, Alignment};
15
16/// Default storage for [`DynObject`](crate::DynObject), and used in [`dyn_trait`](crate::dyn_trait) macro.
17pub type DefaultStorage = RawOrBox<{ 128 * size_of::<usize>() }>;
18
19/// A storage that can be used to store dynamic type-erased objects.
20///
21/// # Safety
22///
23/// `ptr`/`ptr_mut`/`as_ref`/`as_mut`/`as_pinned_mut` must return a pointer/reference
24/// to stored data.
25pub unsafe trait Storage: Sized {
26    /// Constructs a new storage storing `T`.
27    fn new<T>(data: T) -> Self;
28    /// Returns a const pointer to stored data.
29    fn ptr(&self) -> NonNull<()>;
30    /// Returns a mutable pointer to stored data.
31    fn ptr_mut(&mut self) -> NonNull<()>;
32    /// Returns a reference to stored data.
33    ///
34    /// # Safety
35    ///
36    /// Storage must have been constructed with `T`
37    unsafe fn as_ref<T>(&self) -> &T {
38        // SAFETY: `Self::ptr` returns a const pointer to stored data
39        unsafe { self.ptr().cast().as_ref() }
40    }
41    /// Returns a mutable reference to stored data.
42    ///
43    /// # Safety
44    ///
45    /// Storage must have been constructed with `T`
46    unsafe fn as_mut<T>(&mut self) -> &mut T {
47        // SAFETY: `Self::ptr` returns a mutable pointer to stored data
48        unsafe { self.ptr_mut().cast().as_mut() }
49    }
50    /// Returns a pinned mutable reference to stored data.
51    ///
52    /// # Safety
53    ///
54    /// Storage must have been constructed with from `T`
55    unsafe fn as_pinned_mut<T>(self: Pin<&mut Self>) -> Pin<&mut T> {
56        // SAFETY: data is not moved, and `Self::as_mut` as the same precondition
57        unsafe { self.map_unchecked_mut(|this| this.as_mut()) }
58    }
59    /// Drop the storage in place with the layout of the stored data.
60    ///
61    /// Stored data should have been dropped in place before calling this method.
62    ///
63    /// # Safety
64    ///
65    /// `drop_in_place` must be called once, and the storage must not be used
66    /// after. `layout` must be the layout of the data stored.
67    unsafe fn drop_in_place(&mut self, layout: Layout);
68}
69
70/// A storage that can be constructed from boxed data.
71#[cfg(feature = "alloc")]
72pub trait FromBox: Storage {
73    /// Constructs a new storage storing `T`.
74    ///
75    /// Data may be moved out the box if it fits in the storage.
76    fn from_box<T>(boxed: StdBox<T>) -> Self;
77}
78
79/// A raw storage, where data is stored in place.
80///
81/// Data size and alignment must fit, e.g. be lesser or equal to the generic parameters.
82/// This condition is enforced by a constant assertion, which triggers at build time
83/// — **it is not triggered by `cargo check`**.
84#[derive(Debug)]
85#[repr(C)]
86pub struct Raw<const SIZE: usize, const ALIGN: usize = { align_of::<usize>() }>
87where
88    Align<ALIGN>: Alignment,
89{
90    data: MaybeUninit<[u8; SIZE]>,
91    _align: Align<ALIGN>,
92    _not_send_sync: PhantomData<*mut ()>,
93    _pinned: PhantomPinned,
94}
95
96impl<const SIZE: usize, const ALIGN: usize> Raw<SIZE, ALIGN>
97where
98    Align<ALIGN>: Alignment,
99{
100    /// Returns `true` if `T` can be stored in the storage.
101    pub const fn can_store<T>() -> bool {
102        size_of::<T>() <= SIZE && align_of::<T>() <= ALIGN
103    }
104
105    /// Constructs a new `Raw` storage, with compile-time assertion that `T` can be stored.
106    pub const fn new<T>(data: T) -> Self {
107        const { assert!(Self::can_store::<T>()) };
108        // SAFETY: assertion above ensures function contract
109        unsafe { Self::new_unchecked::<T>(data) }
110    }
111
112    /// # Safety
113    ///
114    /// `data` must have size and alignment lesser or equal to the generic parameters.
115    const unsafe fn new_unchecked<T>(data: T) -> Self {
116        let mut raw = Self {
117            data: MaybeUninit::uninit(),
118            _align: Align::NEW,
119            _not_send_sync: PhantomData,
120            _pinned: PhantomPinned,
121        };
122        // SAFETY: function contract guarantees that `raw.data` size and alignment
123        // matches `data` ones; alignment is obtained through `_align` field and `repr(C)`
124        unsafe { raw.data.as_mut_ptr().cast::<T>().write(data) };
125        raw
126    }
127}
128
129// SAFETY: `ptr`/`ptr_mut` return a pointer to the stored data.
130unsafe impl<const SIZE: usize, const ALIGN: usize> Storage for Raw<SIZE, ALIGN>
131where
132    Align<ALIGN>: Alignment,
133{
134    fn new<T>(data: T) -> Self {
135        Self::new(data)
136    }
137    fn ptr(&self) -> NonNull<()> {
138        NonNull::from(&self.data).cast()
139    }
140    fn ptr_mut(&mut self) -> NonNull<()> {
141        NonNull::from(&mut self.data).cast()
142    }
143    unsafe fn drop_in_place(&mut self, _layout: Layout) {}
144}
145
146/// A type-erased [`Box`](StdBox).
147#[cfg(any(feature = "alloc", doc))]
148#[derive(Debug)]
149pub struct Box(NonNull<()>);
150
151#[cfg(feature = "alloc")]
152impl FromBox for Box {
153    fn from_box<T>(data: StdBox<T>) -> Self {
154        Self(NonNull::new(StdBox::into_raw(data).cast()).unwrap())
155    }
156}
157
158// SAFETY: `ptr`/`ptr_mut` return a pointer to the stored data.
159#[cfg(feature = "alloc")]
160unsafe impl Storage for Box {
161    fn new<T>(data: T) -> Self {
162        Self::from_box(StdBox::new(data))
163    }
164    fn ptr(&self) -> NonNull<()> {
165        self.0
166    }
167    fn ptr_mut(&mut self) -> NonNull<()> {
168        self.0
169    }
170    unsafe fn drop_in_place(&mut self, layout: Layout) {
171        if layout.size() != 0 {
172            // SAFETY: storage has been initialized with `Box<T>`,
173            // and `layout` must be `Layout::new::<T>()` as per function contract
174            unsafe { alloc::alloc::dealloc(self.0.as_ptr().cast(), layout) };
175        }
176    }
177}
178
179#[derive(Debug)]
180enum RawOrBoxInner<const SIZE: usize, const ALIGN: usize = { align_of::<usize>() }>
181where
182    Align<ALIGN>: Alignment,
183{
184    Raw(Raw<SIZE, ALIGN>),
185    #[cfg(feature = "alloc")]
186    Box(Box),
187}
188
189/// A [`Raw`] storage with `Box` backup if the object doesn't fit in.
190///
191/// When `alloc` feature is not enabled, it behaves like [`Raw`].
192#[derive(Debug)]
193pub struct RawOrBox<const SIZE: usize, const ALIGN: usize = { align_of::<usize>() }>(
194    RawOrBoxInner<SIZE, ALIGN>,
195)
196where
197    Align<ALIGN>: Alignment;
198
199#[cfg_attr(coverage_nightly, coverage(off))]
200impl<const SIZE: usize, const ALIGN: usize> RawOrBox<SIZE, ALIGN>
201where
202    Align<ALIGN>: Alignment,
203{
204    /// Constructs a [`Raw`] variant of `RawOrBox`.
205    pub const fn new_raw<T>(data: T) -> Self {
206        Self(RawOrBoxInner::Raw(Raw::new(data)))
207    }
208}
209
210#[cfg_attr(coverage_nightly, coverage(off))]
211#[cfg(feature = "alloc")]
212impl<const SIZE: usize, const ALIGN: usize> FromBox for RawOrBox<SIZE, ALIGN>
213where
214    Align<ALIGN>: Alignment,
215{
216    fn from_box<T>(data: StdBox<T>) -> Self {
217        if Raw::<SIZE, ALIGN>::can_store::<T>() {
218            Self::new(*data)
219        } else {
220            Self(RawOrBoxInner::Box(Box::from_box(data)))
221        }
222    }
223}
224
225// SAFETY: The impl delegates to `Raw`/`Box` which implements `Storage`
226// This enum is generic and the variant is chosen according constant predicate,
227// so it's not possible to cover all variant for a specific monomorphization.
228// https://github.com/taiki-e/cargo-llvm-cov/issues/394
229#[cfg_attr(coverage_nightly, coverage(off))]
230unsafe impl<const SIZE: usize, const ALIGN: usize> Storage for RawOrBox<SIZE, ALIGN>
231where
232    Align<ALIGN>: Alignment,
233{
234    fn new<T>(data: T) -> Self {
235        #[cfg(feature = "alloc")]
236        if Raw::<SIZE, ALIGN>::can_store::<T>() {
237            // SAFETY: size and alignment are checked above
238            Self(RawOrBoxInner::Raw(unsafe { Raw::new_unchecked(data) }))
239        } else {
240            Self(RawOrBoxInner::Box(Box::new(data)))
241        }
242        #[cfg(not(feature = "alloc"))]
243        {
244            Self(RawOrBoxInner::Raw(Raw::new(data)))
245        }
246    }
247    fn ptr(&self) -> NonNull<()> {
248        match &self.0 {
249            RawOrBoxInner::Raw(s) => s.ptr(),
250            #[cfg(feature = "alloc")]
251            RawOrBoxInner::Box(s) => s.ptr(),
252        }
253    }
254    fn ptr_mut(&mut self) -> NonNull<()> {
255        match &mut self.0 {
256            RawOrBoxInner::Raw(s) => s.ptr_mut(),
257            #[cfg(feature = "alloc")]
258            RawOrBoxInner::Box(s) => s.ptr_mut(),
259        }
260    }
261    unsafe fn as_ref<T>(&self) -> &T {
262        match &self.0 {
263            // SAFETY: same precondition
264            RawOrBoxInner::Raw(s) if Raw::<SIZE, ALIGN>::can_store::<T>() => unsafe { s.as_ref() },
265            #[cfg(feature = "alloc")]
266            // SAFETY: same precondition
267            RawOrBoxInner::Box(s) if !Raw::<SIZE, ALIGN>::can_store::<T>() => unsafe { s.as_ref() },
268            // SAFETY: storage will always be Raw if it can store `T`
269            _ => unsafe { unreachable_unchecked() },
270        }
271    }
272    unsafe fn as_mut<T>(&mut self) -> &mut T {
273        match &mut self.0 {
274            // SAFETY: same precondition
275            RawOrBoxInner::Raw(s) if Raw::<SIZE, ALIGN>::can_store::<T>() => unsafe { s.as_mut() },
276            #[cfg(feature = "alloc")]
277            // SAFETY: same precondition
278            RawOrBoxInner::Box(s) if !Raw::<SIZE, ALIGN>::can_store::<T>() => unsafe { s.as_mut() },
279            // SAFETY: storage will always be Raw if it can store `T`
280            _ => unsafe { unreachable_unchecked() },
281        }
282    }
283    unsafe fn drop_in_place(&mut self, layout: Layout) {
284        match &mut self.0 {
285            // SAFETY: same precondition
286            RawOrBoxInner::Raw(s) => unsafe { s.drop_in_place(layout) },
287            #[cfg(feature = "alloc")]
288            // SAFETY: same precondition
289            RawOrBoxInner::Box(s) => unsafe { s.drop_in_place(layout) },
290        }
291    }
292}
293
294#[cfg_attr(coverage_nightly, coverage(off))]
295#[cfg(test)]
296#[allow(clippy::undocumented_unsafe_blocks)]
297mod tests {
298    use core::mem;
299
300    use elain::{Align, Alignment};
301
302    use crate::{DynObject, storage::Storage};
303
304    trait Test {}
305    const _: () = {
306        #[derive(Debug)]
307        pub struct __Vtable {
308            __drop_in_place: Option<unsafe fn(::core::ptr::NonNull<()>)>,
309            __layout: ::core::alloc::Layout,
310        }
311        impl<'__lt> crate::object::DynTrait for dyn Test + '__lt {
312            type Vtable = __Vtable;
313            fn drop_in_place_fn(
314                vtable: &Self::Vtable,
315            ) -> Option<unsafe fn(core::ptr::NonNull<()>)> {
316                vtable.__drop_in_place
317            }
318            fn layout(vtable: &Self::Vtable) -> core::alloc::Layout {
319                vtable.__layout
320            }
321        }
322        unsafe impl<'__lt, __Dyn: Test + '__lt> crate::object::Vtable<__Dyn> for dyn Test + '__lt {
323            fn vtable<__Storage: crate::storage::Storage>() -> &'static Self::Vtable {
324                &const {
325                    __Vtable {
326                        __drop_in_place: <Self as crate::object::Vtable<__Dyn>>::DROP_IN_PLACE_FN,
327                        __layout: core::alloc::Layout::new::<__Dyn>(),
328                    }
329                }
330            }
331        }
332    };
333    impl Test for () {}
334    impl<const N: usize> Test for [u8; N] {}
335    impl Test for u64 {}
336    type TestObject<'__dyn, S> = DynObject<dyn Test + '__dyn, S>;
337
338    #[test]
339    fn raw_alignment() {
340        fn check_alignment<const ALIGN: usize>()
341        where
342            Align<ALIGN>: Alignment,
343        {
344            let storages = [(); 2].map(TestObject::<super::Raw<0, ALIGN>>::new);
345            for s in &storages {
346                assert!(s.storage().ptr().cast::<Align<ALIGN>>().is_aligned());
347            }
348            const { assert!(ALIGN < 2048) };
349            assert!(
350                storages
351                    .iter()
352                    .any(|s| !s.storage().ptr().cast::<Align<2048>>().is_aligned())
353            );
354        }
355        check_alignment::<1>();
356        check_alignment::<8>();
357        check_alignment::<64>();
358        check_alignment::<1024>();
359    }
360
361    #[cfg(feature = "alloc")]
362    #[test]
363    fn raw_or_box() {
364        fn check_variant<const N: usize>(variant: impl Fn(&super::RawOrBox<8>) -> bool) {
365            let array = core::array::from_fn::<u8, N, _>(|i| i as u8);
366            let storage = TestObject::<super::RawOrBox<8>>::new(array);
367            assert!(variant(storage.storage()));
368            assert_eq!(
369                unsafe { storage.storage().ptr().cast::<[u8; N]>().read() },
370                array
371            );
372        }
373        check_variant::<4>(|s| matches!(s.0, super::RawOrBoxInner::Raw(_)));
374        check_variant::<64>(|s| matches!(s.0, super::RawOrBoxInner::Box(_)));
375
376        let storage = TestObject::<super::RawOrBox<8, 1>>::new(0u64);
377        assert!(matches!(storage.storage().0, super::RawOrBoxInner::Box(_)));
378    }
379
380    struct SetDropped<'a>(&'a mut bool);
381    impl Test for SetDropped<'_> {}
382    impl Drop for SetDropped<'_> {
383        fn drop(&mut self) {
384            assert!(!mem::replace(self.0, true));
385        }
386    }
387
388    #[test]
389    fn storage_drop() {
390        fn check_drop<S: Storage>() {
391            let mut dropped = false;
392            let storage = TestObject::<S>::new(SetDropped(&mut dropped));
393            assert!(!*unsafe { storage.storage().ptr().cast::<SetDropped>().as_ref() }.0);
394            drop(storage);
395            assert!(dropped);
396        }
397        check_drop::<super::Raw<{ size_of::<SetDropped>() }, { align_of::<SetDropped>() }>>();
398        #[cfg(feature = "alloc")]
399        check_drop::<super::Box>();
400        #[cfg(feature = "alloc")]
401        check_drop::<super::RawOrBox<{ size_of::<SetDropped>() }>>();
402        #[cfg(feature = "alloc")]
403        check_drop::<super::RawOrBox<0>>();
404    }
405
406    #[test]
407    fn storage_dst() {
408        fn check_dst<S: Storage>() {
409            drop(TestObject::<S>::new(()));
410        }
411        check_dst::<super::Raw<{ size_of::<SetDropped>() }, { align_of::<SetDropped>() }>>();
412        #[cfg(feature = "alloc")]
413        check_dst::<super::Box>();
414        #[cfg(feature = "alloc")]
415        check_dst::<super::RawOrBox<{ size_of::<SetDropped>() }>>();
416        #[cfg(feature = "alloc")]
417        check_dst::<super::RawOrBox<0>>();
418    }
419}