dyn_utils/
object.rs

1//! [`DynObject`] implementation.
2use core::{alloc::Layout, any::Any, fmt, marker::PhantomData, mem, pin::Pin, ptr::NonNull};
3
4use crate::{
5    impls::any_impl,
6    storage::{DefaultStorage, Storage},
7};
8
9/// A trait object whose data is stored in a generic [`Storage`].
10///
11/// [`dyn_object`] proc-macro can be used to make a trait compatible with `DynObject`.
12///
13/// # Examples
14///
15/// ```rust
16/// # use dyn_utils::DynObject;
17/// let future: DynObject<dyn Future<Output = usize>> = DynObject::new(async { 42 });
18/// # futures::executor::block_on(async move {
19/// assert_eq!(future.await, 42);
20/// # });
21/// ```
22///
23/// [`dyn_object`]: crate::dyn_object
24pub struct DynObject<Dyn: DynTrait + ?Sized, S: Storage = DefaultStorage> {
25    storage: S,
26    vtable: &'static Dyn::Vtable,
27    _phantom: PhantomData<Dyn>,
28}
29
30// SAFETY: DynObject is just a wrapper around `Dyn`
31unsafe impl<Dyn: Send + DynTrait + ?Sized, S: Storage> Send for DynObject<Dyn, S> {}
32
33// SAFETY: DynObject is just a wrapper around `Dyn`
34unsafe impl<Dyn: Sync + DynTrait + ?Sized, S: Storage> Sync for DynObject<Dyn, S> {}
35
36impl<Dyn: Unpin + DynTrait + ?Sized, S: Storage> Unpin for DynObject<Dyn, S> {}
37
38impl<S: Storage, Dyn: DynTrait + ?Sized> DynObject<Dyn, S> {
39    /// Constructs a new `DynObject` from an object implementing the trait
40    pub fn new<T>(object: T) -> Self
41    where
42        Dyn: Vtable<T>,
43    {
44        Self {
45            storage: S::new(object),
46            vtable: Dyn::vtable::<S>(),
47            _phantom: PhantomData,
48        }
49    }
50
51    /// Construct a new `DynObject` from a boxed object implementing the trait
52    #[cfg(feature = "alloc")]
53    pub fn from_box<T>(boxed: alloc::boxed::Box<T>) -> Self
54    where
55        S: crate::storage::FromBox,
56        Dyn: Vtable<T>,
57    {
58        Self {
59            storage: S::from_box(boxed),
60            vtable: Dyn::vtable::<S>(),
61            _phantom: PhantomData,
62        }
63    }
64
65    #[doc(hidden)]
66    pub fn vtable(&self) -> &'static Dyn::Vtable {
67        self.vtable
68    }
69
70    #[doc(hidden)]
71    pub fn storage(&self) -> &S {
72        &self.storage
73    }
74
75    #[doc(hidden)]
76    pub fn storage_mut(&mut self) -> &mut S {
77        &mut self.storage
78    }
79
80    #[doc(hidden)]
81    pub fn storage_pinned_mut(self: Pin<&mut Self>) -> Pin<&mut S> {
82        // SAFETY: `self.storage` is structurally pinned
83        unsafe { self.map_unchecked_mut(|this| &mut this.storage) }
84    }
85
86    #[doc(hidden)]
87    pub fn insert<T>(this: &mut Option<Self>, object: T) -> &mut T
88    where
89        Dyn: Vtable<T>,
90    {
91        let storage = this.insert(DynObject::new(object));
92        // SAFETY: storage has been initialized with `T`
93        unsafe { storage.storage_mut().as_mut::<T>() }
94    }
95
96    #[doc(hidden)]
97    pub fn insert_pinned<T>(this: Pin<&mut Option<Self>>, object: T) -> Pin<&mut T>
98    where
99        Dyn: Vtable<T>,
100    {
101        // SAFETY: the returned reference cannot is structurally pinned
102        unsafe { this.map_unchecked_mut(|opt| Self::insert(opt, object)) }
103    }
104}
105
106impl<Dyn: DynTrait + ?Sized, S: Storage> Drop for DynObject<Dyn, S> {
107    fn drop(&mut self) {
108        if let Some(drop_inner) = Dyn::drop_in_place_fn(self.vtable) {
109            // SAFETY: the storage data is no longer accessed after the call,
110            // and is matched by the vtable as per function contract.
111            unsafe { drop_inner(self.storage.ptr_mut()) };
112        }
113        let layout = Dyn::layout(self.vtable);
114        // SAFETY: the storage data is no longer accessed after the call,
115        // and is matched by the vtable as per function contract.
116        unsafe { self.storage.drop_in_place(layout) };
117    }
118}
119
120#[cfg_attr(coverage_nightly, coverage(off))]
121impl<Dyn: DynTrait<Vtable: fmt::Debug> + ?Sized, S: Storage + fmt::Debug> fmt::Debug
122    for DynObject<Dyn, S>
123{
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        f.debug_struct("DynObject")
126            .field("inner", &self.storage)
127            .field("vtable", &self.vtable)
128            .finish()
129    }
130}
131// Putting this in impls module make these methods appears before others,
132// so it has to be explicitly put after other methods
133any_impl!(dyn Any);
134any_impl!(dyn Any + Send);
135any_impl!(dyn Any + Send + Sync);
136
137/// A trait object with its associated vtable.
138pub trait DynTrait {
139    /// The trait object vtable.
140    type Vtable: 'static;
141    /// Returns the drop function of the trait object as stored in vtable.
142    fn drop_in_place_fn(vtable: &Self::Vtable) -> Option<unsafe fn(NonNull<()>)>;
143    /// Returns the layout of the trait object as stored in vtable.
144    fn layout(vtable: &Self::Vtable) -> Layout;
145}
146
147/// A vtable constructor.
148///
149/// # Safety
150///
151/// - `DynTrait::layout` must return `core::alloc::Layout::new::<T>()`.
152/// - `DynTrait::drop_in_place_fn` must returns
153///   `<Self as crate::object::Vtable<T>>::DROP_IN_PLACE_FN`
154pub unsafe trait Vtable<T>: DynTrait {
155    /// Returns the vtable for a given `T` stored in `S`.
156    fn vtable<S: Storage>() -> &'static Self::Vtable;
157    /// The function has the same safety contract that [`core::ptr::drop_in_place`].
158    const DROP_IN_PLACE_FN: Option<unsafe fn(NonNull<()>)> = if mem::needs_drop::<T>() {
159        // SAFETY: as per function contract
160        Some(|ptr_mut| unsafe { ptr_mut.cast::<T>().drop_in_place() })
161    } else {
162        None
163    };
164}
165
166#[cfg(test)]
167mod tests {
168    use crate::{impls::any_test, object::Any};
169
170    any_test!(dyn_any, dyn Any);
171    any_test!(dyn_any_send, dyn Any + Send);
172    any_test!(dyn_any_send_sync, dyn Any + Send + Sync);
173}