rootcause_internals/attachment/
vtable.rs

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