rootcause_internals/attachment/
vtable.rs

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