dungeon_cell/core/inner/
cleanup.rs

1//! Trait for cleanup of a [`Inner`].
2
3use crate::align::Aligned;
4use crate::bound::{traits, Bound};
5use crate::buffer::Buffer;
6use crate::compile_time::const_transmute;
7use crate::marker_traits::{IsBound, IsLayout};
8use crate::vtable::VTable;
9
10use super::Inner;
11
12/// Trait implemented on layouts that can be cleaned up be a vtable.
13///
14/// The trait bound `L: Cleanup<V>` signifies that the vtable `V` can cleanup a value
15/// with layout `L`.
16///
17/// This trait is implemented for all `V: VTable` and [`Layout`][crate::layout::Layout].
18///
19/// The associated type [`Self::Storage`] will be cleaned up automatically when dropped.
20/// This type is based on the vtable and the layout.
21///
22/// For dynamic bounds on the vtable that indicate the value is `Copy` then the associated type
23/// will perform no cleanup operation. For all other cases the type will run the
24/// drop impl from the vtable.
25///
26/// This trait is [sealed](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed)
27/// and cannot be implemented by external types.
28///
29/// # Safety
30/// [`Self::Storage`] must only be `InnerNoDrop` or `InnerDrop`.
31pub unsafe trait Cleanup<V>: IsLayout {
32    /// Storage for [`DungeonCore`][super::super::DungeonCore].
33    type Storage;
34}
35
36// SAFETY: CleanupHelper follows the requirements of Cleanup.
37unsafe impl<V: VTable, L: IsLayout> Cleanup<V> for L
38where
39    L: CleanupHelper<V, V::Bounds>,
40{
41    type Storage = <L as CleanupHelper<V, V::Bounds>>::Inner;
42}
43
44pub trait CleanupHelper<V, B> {
45    type Inner;
46}
47
48// /// Inner storage bounded by dynamic bound.
49// ///
50// /// This is the same as a [`Inner`] but with a stored dynamic trait bound.
51// /// This type doesn't force the data stored in `inner` to be actually bounded
52// /// by this dynamic trait bound.
53// ///
54// /// This type will implement/not implement [`Send`], [`Sync`], and [`Unpin`] if
55// /// the dynamic bound implies the value in `inner` does.
56// #[repr(transparent)]
57// struct BoundedInner<L: IsLayout, B: DynamicBound, V> {
58//     /// The aligned buffer and vtable.
59//     inner: Inner<L, V>,
60//
61//     /// Marker to cause the auto impl of `Send` to be enabled/disabled.
62//     _bound_marker: PhantomData<B::Marker>,
63// }
64//
65// impl<L: IsLayout, B: DynamicBound, V: Copy> Copy for BoundedInner<L, B, V> {}
66// impl<L: IsLayout, B: DynamicBound, V: Clone> Clone for BoundedInner<L, B, V> {
67//     fn clone(&self) -> Self {
68//         Self {
69//             inner: self.inner.clone(),
70//             _bound_marker: PhantomData,
71//         }
72//     }
73// }
74
75/// Inner storage that may be [`Copy`] and without a [`Drop`].
76#[repr(transparent)]
77pub struct InnerNoDrop<L: IsLayout, V> {
78    /// Dynamically bounded storage.
79    bounded: Inner<L, V>,
80}
81
82impl<L: IsLayout, V: Copy> Copy for InnerNoDrop<L, V> {}
83impl<L: IsLayout, V: Clone> Clone for InnerNoDrop<L, V> {
84    fn clone(&self) -> Self {
85        Self {
86            bounded: self.bounded.clone(),
87        }
88    }
89}
90
91/// Inner storage with a [`Drop`].
92#[repr(transparent)]
93pub struct InnerDrop<L: IsLayout, V: VTable> {
94    /// Dynamically bounded storage.
95    bounded: Inner<L, V>,
96}
97
98impl<L: IsLayout, V: VTable + Clone> Clone for InnerDrop<L, V>
99where
100    <V::Bounds as IsBound>::CloneMarker: Clone,
101{
102    fn clone(&self) -> Self {
103        // SAFETY: The vtable isn't changed.
104        let (buffer, vtable) = unsafe { self.bounded.parts() };
105
106        let mut temp_buffer = Aligned::<_, L::Alignment>::new(Buffer::uninit());
107
108        // SAFETY: We bounded on the CloneMarker implementing Clone.
109        // And the buffer has a value for the vtable by the inveriants of Inner.
110        // The temp_buffer is aligned properly by Aligned.
111        unsafe {
112            vtable
113                .bound_impl()
114                .clone_value(buffer.as_ptr(), temp_buffer.as_mut_ptr())
115        };
116
117        Self {
118            // SAFETY: the vtable is for the buffer because it was before.
119            bounded: unsafe {
120                Inner::new(temp_buffer.value, self.bounded.vtable().clone())
121            },
122        }
123    }
124}
125
126impl<L: IsLayout, V: VTable> Drop for InnerDrop<L, V> {
127    fn drop(&mut self) {
128        // SAFETY: We will drop the value in the buffer and then nothing else will be able
129        // to access the `Inner`.
130        let (buffer, vtable) = unsafe { self.bounded.parts_mut() };
131
132        // SAFETY: Because of the requirement on `Inner` we know that `buffer` must contain a valid value
133        // of the type the vtable represents.
134        unsafe { vtable.bound_impl().drop_value(buffer.as_mut_ptr()) }
135    }
136}
137
138/// Get the [`Inner<L, V>`] that the associated type contains.
139pub const fn as_inner<L: IsLayout + Cleanup<V>, V>(
140    inner: &<L as Cleanup<V>>::Storage,
141) -> &Inner<L, V> {
142    // SAFETY: Cleanup is only implemented for 2 Inner types: `InnerNoDrop` and `InnerDrop`.
143    // Both of these are transparent repr to `BoundedInner<L, B, V>` which is transparent repr to
144    // `Inner<L, V>`. As such, `<V as Cleanup<L, B>>::Inner` and `Inner<L, V>` have the same layout
145    // and can be transmuted between.
146    unsafe {
147        &*(inner as *const <L as Cleanup<V>>::Storage as *const Inner<L, V>)
148    }
149}
150
151/// Get the [`Inner<L, V>`] that the associated type contains mutably.
152pub fn as_inner_mut<L: IsLayout + Cleanup<V>, V>(
153    inner: &mut <L as Cleanup<V>>::Storage,
154) -> &mut Inner<L, V> {
155    // SAFETY: Cleanup is only implemented for 2 Inner types: `InnerNoDrop` and `InnerDrop`.
156    // Both of these are transparent repr to `BoundedInner<L, B, V>` which is transparent repr to
157    // `Inner<L, V>`. As such, `<V as Cleanup<L, B>>::Inner` and `Inner<L, V>` have the same layout
158    // and can be transmuted between.
159    unsafe {
160        &mut *(inner as *mut <L as Cleanup<V>>::Storage as *mut Inner<L, V>)
161    }
162}
163
164/// Convert the associated type into the [`Inner<L, V>`] it contains.
165pub const fn into_inner<L: IsLayout + Cleanup<V>, V>(
166    inner: <L as Cleanup<V>>::Storage,
167) -> Inner<L, V> {
168    // SAFETY: Cleanup is only implemented for 2 Inner types: `InnerNoDrop` and `InnerDrop`.
169    // Both of these are transparent repr to `BoundedInner<L, B, V>` which is transparent repr to
170    // `Inner<L, V>`. As such, `<V as Cleanup<L, B>>::Inner` and `Inner<L, V>` have the same layout
171    // and can be transmuted between.
172    unsafe { const_transmute(inner) }
173}
174
175/// Convert a [`Inner<L, V>`] to the associated type.
176pub const fn from_inner<L: IsLayout + Cleanup<V>, V>(
177    inner: Inner<L, V>,
178) -> <L as Cleanup<V>>::Storage {
179    // SAFETY: Cleanup is only implemented for 2 Inner types: `InnerNoDrop` and `InnerDrop`.
180    // Both of these are transparent repr to `BoundedInner<L, B, V>` which is transparent repr to
181    // `Inner<L, V>`. As such, `<V as Cleanup<L, B>>::Inner` and `Inner<L, V>` have the same layout
182    // and can be transmuted between.
183    unsafe { const_transmute(inner) }
184}
185
186// Make the associated type for a dynamic bound with `Copy` the `InnerNoDrop` storage.
187//
188// SAFETY: This gives out `InnerNoDrop`, but it makes sure the bound has `Copy`.
189// Other code must bound any value it stores in the buffer by the bound given.
190impl<L: IsLayout, V: VTable, Send, Sync, Clone, Unpin, Debug>
191    CleanupHelper<V, Bound<Send, Sync, traits::Copy, Clone, Unpin, Debug>> for L
192where
193    V: VTable<Bounds = Bound<Send, Sync, traits::Copy, Clone, Unpin, Debug>>,
194{
195    /// The inner storage will not run `Drop` code and can be `Copy` if the vtable is.
196    type Inner = InnerNoDrop<L, V>;
197}
198
199// Make the associated type for a dynamic bound without `Copy` the `InnerDrop` storage.
200// This storage will drop the value using the vtable.
201//
202// SAFETY: This gives out `InnerDrop` which has no special rules.
203// Because `InnerDrop` doesn't expose the `Inner` impl of `Copy` we don't
204// have to worry about aliasing the drop unless other code breaks a safety rule.
205impl<L: IsLayout, V: VTable, Send, Sync, Clone, Unpin, Debug>
206    CleanupHelper<V, Bound<Send, Sync, traits::__, Clone, Unpin, Debug>> for L
207where
208    V: VTable<Bounds = Bound<Send, Sync, traits::__, Clone, Unpin, Debug>>,
209{
210    /// The inner stoage will run `Drop` code from the vtable.
211    type Inner = InnerDrop<L, V>;
212}
213
214#[cfg(test)]
215mod test {
216    use crate::bound::bounds;
217    use crate::buffer::Buffer;
218    use crate::layout::{self, Alignment, Size};
219    use crate::vtable::{StaticVTable, VTableOf};
220
221    use super::*;
222
223    #[test]
224    fn inner_with_bound_without_copy_does_drop_stored_value() {
225        use std::sync::atomic::{AtomicBool, Ordering};
226
227        static FLAG: AtomicBool = AtomicBool::new(false);
228
229        struct MyType {
230            value: i32,
231        }
232
233        impl Drop for MyType {
234            fn drop(&mut self) {
235                assert_eq!(self.value, 123);
236
237                FLAG.store(true, Ordering::Relaxed);
238            }
239        }
240
241        type Bound = bounds::Empty;
242        type Layout = layout::Layout<Size<4>, Alignment<4>>;
243
244        let value = MyType { value: 123 };
245
246        let buffer = Buffer::new(value);
247
248        // SAFETY: The vtable is for the value in the buffer.
249        let inner = unsafe {
250            Inner::<Layout, _>::new(
251                buffer,
252                <&'static StaticVTable<Bound> as VTableOf<MyType>>::instance(),
253            )
254        };
255
256        let stored = from_inner::<_, _>(inner);
257
258        // The drop hasn't happened yet.
259        assert!(!FLAG.load(Ordering::Relaxed));
260
261        drop(stored);
262
263        // Now the drop has happened because we dropped `stored`.
264        assert!(FLAG.load(Ordering::Relaxed));
265    }
266
267    #[test]
268    fn inner_with_bound_with_copy_implements_copy() {
269        #[derive(Clone, Copy, PartialEq, Debug)]
270        struct MyType {
271            value: i32,
272        }
273
274        type VTable = &'static StaticVTable<Bound>;
275        type Bound = bounds::Copy;
276        type Layout = layout::Layout<Size<4>, Alignment<4>>;
277
278        let value = MyType { value: 123 };
279
280        let buffer = Buffer::new(value);
281
282        // SAFETY: The vtable is for the value in the buffer.
283        let inner = unsafe {
284            Inner::<Layout, _>::new(
285                buffer,
286                <VTable as VTableOf<MyType>>::instance(),
287            )
288        };
289
290        let stored = from_inner::<_, _>(inner);
291
292        // We implicitly invoke Copy here.
293        let stored_copy = stored;
294
295        let inner_original = into_inner::<Layout, VTable>(stored);
296        let inner_copy = into_inner::<Layout, VTable>(stored_copy);
297
298        // SAFETY: We know the type in the buffer is `MyType`.
299        let original: MyType =
300            unsafe { Buffer::into_value(inner_original.into_parts().0) };
301
302        // SAFETY: We know the type in the buffer is `MyType`.
303        let copy: MyType =
304            unsafe { Buffer::into_value(inner_copy.into_parts().0) };
305
306        assert_eq!(copy, original);
307    }
308}