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