rootcause_internals/attachment/
vtable.rs

1use alloc::boxed::Box;
2use core::{any::TypeId, ptr::NonNull};
3
4use crate::{
5    attachment::{data::AttachmentData, raw::RawAttachmentRef},
6    handlers::{AttachmentFormattingStyle, AttachmentHandler, FormattingFunction},
7    util::Erased,
8};
9
10/// Vtable for type-erased attachment operations.
11///
12/// Contains function pointers for performing operations on attachments without
13/// knowing their concrete type at compile time.
14pub(super) struct AttachmentVtable {
15    type_id: fn() -> TypeId,
16    handler_type_id: fn() -> TypeId,
17    drop: unsafe fn(NonNull<AttachmentData<Erased>>),
18    display: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
19    debug: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
20    preferred_formatting_style:
21        unsafe fn(RawAttachmentRef<'_>, FormattingFunction, bool) -> AttachmentFormattingStyle,
22}
23
24impl AttachmentVtable {
25    /// Creates a new [`AttachmentVtable`] for the attachment type `A` and the handler type `H`.
26    pub(super) const fn new<A: 'static, H: AttachmentHandler<A>>() -> &'static Self {
27        &Self {
28            type_id: TypeId::of::<A>,
29            handler_type_id: TypeId::of::<H>,
30            drop: drop::<A>,
31            display: display::<A, H>,
32            debug: debug::<A, H>,
33            preferred_formatting_style: preferred_formatting_style::<A, H>,
34        }
35    }
36
37    /// Gets the [`TypeId`] of the attachment type that was used to create this [`AttachmentVtable`].
38    pub(super) fn type_id(&self) -> TypeId {
39        (self.type_id)()
40    }
41
42    /// Gets the [`TypeId`] of the handler that was used to create this [`AttachmentVtable`].
43    pub(super) fn handler_type_id(&self) -> TypeId {
44        (self.handler_type_id)()
45    }
46
47    /// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this pointer.
48    ///
49    /// # Safety
50    ///
51    /// - The caller must ensure that the pointer comes from a [`Box<AttachmentData<A>>`], which was turned
52    ///   into a pointer using [`Box::into_raw`].
53    /// - The attachment type `A` stored in the [`AttachmentData`] must match the `A` used when creating this [`AttachmentVtable`].
54    /// - After calling this method, the pointer must no longer be used.
55    pub(super) unsafe fn drop(&self, ptr: NonNull<AttachmentData<Erased>>) {
56        // SAFETY: We know that `self.drop` points to the function `drop::<A>` below.
57        // That function has three requirements, all of which are guaranteed by our caller:
58        // - The pointer must come from `Box::into_raw`
59        // - The attachment type `A` must match the stored type
60        // - The pointer must not be used after calling
61        unsafe {
62            (self.drop)(ptr);
63        }
64    }
65
66    /// Formats the attachment using the [`H::display`] function
67    /// used when creating this [`AttachmentVtable`].
68    ///
69    /// [`H::display`]: AttachmentHandler::display
70    ///
71    /// # Safety
72    ///
73    /// The attachment type `A` used when creating this [`AttachmentVtable`] must match the type
74    /// stored in the [`RawAttachmentRef`].
75    pub(super) unsafe fn display<'a>(
76        &self,
77        ptr: RawAttachmentRef<'a>,
78        formatter: &mut core::fmt::Formatter<'_>,
79    ) -> core::fmt::Result {
80        // SAFETY: We know that the `self.display` field points to the function `display::<A, H>` below.
81        // That function requires that the attachment type `A` matches the actual attachment type stored in the `AttachmentData`,
82        // which is guaranteed by our caller.
83        unsafe { (self.display)(ptr, formatter) }
84    }
85
86    /// Formats the attachment using the [`H::debug`] function
87    /// used when creating this [`AttachmentVtable`].
88    ///
89    /// [`H::debug`]: AttachmentHandler::debug
90    ///
91    /// # Safety
92    ///
93    /// The attachment type `A` used when creating this [`AttachmentVtable`] must match the type
94    /// stored in the [`RawAttachmentRef`].
95    pub(super) unsafe fn debug<'a>(
96        &self,
97        ptr: RawAttachmentRef<'a>,
98        formatter: &mut core::fmt::Formatter<'_>,
99    ) -> core::fmt::Result {
100        // SAFETY: We know that the `self.debug` field points to the function `debug::<A, H>` below.
101        // That function requires that the attachment type `A` matches the actual attachment type stored in the `AttachmentData`,
102        // which is guaranteed by our caller.
103        unsafe { (self.debug)(ptr, formatter) }
104    }
105
106    /// Gets the preferred formatting style using the [`H::preferred_formatting_style`] function
107    /// used when creating this [`AttachmentVtable`].
108    ///
109    /// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
110    ///
111    /// # Safety
112    ///
113    /// The attachment type `A` used when creating this [`AttachmentVtable`] must match the type
114    /// stored in the [`RawAttachmentRef`].
115    pub(super) unsafe fn preferred_formatting_style<'a>(
116        &self,
117        ptr: RawAttachmentRef<'a>,
118        report_formatting_function: FormattingFunction,
119        report_formatting_alternate: bool,
120    ) -> AttachmentFormattingStyle {
121        // SAFETY: We know that the `self.preferred_formatting_style` field points to the function `preferred_formatting_style::<A, H>` below.
122        // That function requires that the attachment type `A` matches the actual attachment type stored in the `AttachmentData`,
123        // which is guaranteed by our caller.
124        unsafe {
125            (self.preferred_formatting_style)(
126                ptr,
127                report_formatting_function,
128                report_formatting_alternate,
129            )
130        }
131    }
132}
133
134/// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this pointer.
135///
136/// # Safety
137///
138/// - The caller must ensure that the pointer comes from a [`Box<AttachmentData<A>>`], which was turned
139///   into a pointer using [`Box::into_raw`].
140/// - The attachment type `A` must match the actual attachment type stored in the [`AttachmentData`].
141/// - After calling this method, the pointer must no longer be used.
142unsafe fn drop<A: 'static>(ptr: NonNull<AttachmentData<Erased>>) {
143    let ptr: NonNull<AttachmentData<A>> = ptr.cast();
144    let ptr = ptr.as_ptr();
145    // SAFETY: Our pointer has the correct type as guaranteed by the caller, and it came
146    // from a call to [`Box::into_raw`] as also guaranteed by our caller.
147    let boxed = unsafe { Box::from_raw(ptr) };
148    core::mem::drop(boxed);
149}
150
151/// Formats an attachment using its handler's display implementation.
152///
153/// # Safety
154///
155/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
156unsafe fn display<A: 'static, H: AttachmentHandler<A>>(
157    ptr: RawAttachmentRef<'_>,
158    formatter: &mut core::fmt::Formatter<'_>,
159) -> core::fmt::Result {
160    // SAFETY: Our caller guarantees that the type `A` matches the actual attachment type stored in the `AttachmentData`.
161    let context: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
162    H::display(context, formatter)
163}
164
165/// Formats an attachment using its handler's debug implementation.
166///
167/// # Safety
168///
169/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
170unsafe fn debug<A: 'static, H: AttachmentHandler<A>>(
171    ptr: RawAttachmentRef<'_>,
172    formatter: &mut core::fmt::Formatter<'_>,
173) -> core::fmt::Result {
174    // SAFETY: Our caller guarantees that the type `A` matches the actual attachment type stored in the `AttachmentData`.
175    let context: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
176    H::debug(context, formatter)
177}
178
179/// Gets the preferred formatting style using the [`H::preferred_formatting_style`] function.
180///
181/// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
182///
183/// # Safety
184///
185/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
186unsafe fn preferred_formatting_style<A: 'static, H: AttachmentHandler<A>>(
187    ptr: RawAttachmentRef<'_>,
188    report_formatting_function: FormattingFunction,
189    report_formatting_alternate: bool,
190) -> AttachmentFormattingStyle {
191    // SAFETY: Our caller guarantees that the type `A` matches the actual attachment type stored in the `AttachmentData`.
192    let context: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
193    H::preferred_formatting_style(
194        context,
195        report_formatting_function,
196        report_formatting_alternate,
197    )
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use crate::handlers::AttachmentHandler;
204
205    struct HandlerI32;
206    impl AttachmentHandler<i32> for HandlerI32 {
207        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208            core::fmt::Display::fmt(value, formatter)
209        }
210
211        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
212            core::fmt::Debug::fmt(value, formatter)
213        }
214    }
215
216    #[test]
217    fn test_attachment_vtable_eq() {
218        // Test that vtables have proper static lifetime and can be safely shared
219        let vtable1 = AttachmentVtable::new::<i32, HandlerI32>();
220        let vtable2 = AttachmentVtable::new::<i32, HandlerI32>();
221
222        // Both should be the exact same static instance
223        assert!(core::ptr::eq(vtable1, vtable2));
224    }
225
226    #[test]
227    fn test_attachment_type_id() {
228        let vtable = AttachmentVtable::new::<i32, HandlerI32>();
229        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
230    }
231}