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) -> 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(
76        &self,
77        ptr: RawAttachmentRef<'_>,
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(
96        &self,
97        ptr: RawAttachmentRef<'_>,
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(
116        &self,
117        ptr: RawAttachmentRef<'_>,
118        report_formatting_function: FormattingFunction,
119    ) -> AttachmentFormattingStyle {
120        // SAFETY: We know that the `self.preferred_formatting_style` field points to the function `preferred_formatting_style::<A, H>` below.
121        // That function requires that the attachment type `A` matches the actual attachment type stored in the `AttachmentData`,
122        // which is guaranteed by our caller.
123        unsafe { (self.preferred_formatting_style)(ptr, report_formatting_function) }
124    }
125}
126
127/// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this pointer.
128///
129/// # Safety
130///
131/// - The caller must ensure that the pointer comes from a [`Box<AttachmentData<A>>`], which was turned
132///   into a pointer using [`Box::into_raw`].
133/// - The attachment type `A` must match the actual attachment type stored in the [`AttachmentData`].
134/// - After calling this method, the pointer must no longer be used.
135unsafe fn drop<A: 'static>(ptr: NonNull<AttachmentData<Erased>>) {
136    let ptr: NonNull<AttachmentData<A>> = ptr.cast();
137    let ptr = ptr.as_ptr();
138    // SAFETY: Our pointer has the correct type as guaranteed by the caller, and it came
139    // from a call to [`Box::into_raw`] as also guaranteed by our caller.
140    let boxed = unsafe { Box::from_raw(ptr) };
141    core::mem::drop(boxed);
142}
143
144/// Formats an attachment using its handler's display implementation.
145///
146/// # Safety
147///
148/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
149unsafe fn display<A: 'static, H: AttachmentHandler<A>>(
150    ptr: RawAttachmentRef<'_>,
151    formatter: &mut core::fmt::Formatter<'_>,
152) -> core::fmt::Result {
153    // SAFETY: Our caller guarantees that the type `A` matches the actual attachment type stored in the `AttachmentData`.
154    let context: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
155    H::display(context, formatter)
156}
157
158/// Formats an attachment using its handler's debug implementation.
159///
160/// # Safety
161///
162/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
163unsafe fn debug<A: 'static, H: AttachmentHandler<A>>(
164    ptr: RawAttachmentRef<'_>,
165    formatter: &mut core::fmt::Formatter<'_>,
166) -> core::fmt::Result {
167    // SAFETY: Our caller guarantees that the type `A` matches the actual attachment type stored in the `AttachmentData`.
168    let context: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
169    H::debug(context, formatter)
170}
171
172/// Gets the preferred formatting style using the [`H::preferred_formatting_style`] function.
173///
174/// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
175///
176/// # Safety
177///
178/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
179unsafe fn preferred_formatting_style<A: 'static, H: AttachmentHandler<A>>(
180    ptr: RawAttachmentRef<'_>,
181    report_formatting_function: FormattingFunction,
182) -> AttachmentFormattingStyle {
183    // SAFETY: Our caller guarantees that the type `A` matches the actual attachment type stored in the `AttachmentData`.
184    let context: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
185    H::preferred_formatting_style(context, report_formatting_function)
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::handlers::AttachmentHandler;
192
193    struct HandlerI32;
194    impl AttachmentHandler<i32> for HandlerI32 {
195        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
196            core::fmt::Display::fmt(value, formatter)
197        }
198
199        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
200            core::fmt::Debug::fmt(value, formatter)
201        }
202    }
203
204    #[test]
205    fn test_attachment_vtable_eq() {
206        // Test that vtables have proper static lifetime and can be safely shared
207        let vtable1 = AttachmentVtable::new::<i32, HandlerI32>();
208        let vtable2 = AttachmentVtable::new::<i32, HandlerI32>();
209
210        // Both should be the exact same static instance
211        assert!(core::ptr::eq(vtable1, vtable2));
212    }
213
214    #[test]
215    fn test_attachment_type_id() {
216        let vtable = AttachmentVtable::new::<i32, HandlerI32>();
217        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
218    }
219}