Skip to main content

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::{
21    any::{self, Any, TypeId},
22    ptr::NonNull,
23};
24
25use crate::{
26    attachment::{
27        data::AttachmentData,
28        raw::{RawAttachmentMut, RawAttachmentRef},
29    },
30    handlers::{AttachmentFormattingStyle, AttachmentHandler, FormattingFunction},
31    util::Erased,
32};
33
34/// Vtable for type-erased attachment operations.
35///
36/// Contains function pointers for performing operations on attachments without
37/// knowing their concrete type at compile time.
38///
39/// # Safety Invariant
40///
41/// The fields `drop`, `display`, `debug`, and `preferred_formatting_style` are
42/// guaranteed to point to the functions defined below instantiated with the
43/// attachment type `A` and handler type `H` that were used to create this
44/// [`AttachmentVtable`].
45pub(crate) struct AttachmentVtable {
46    /// Gets the [`TypeId`] of the attachment type that was used to create this
47    /// [`AttachmentVtable`].
48    type_id: fn() -> TypeId,
49    /// Gets the [`any::type_name`] of the attachment type that was used to
50    /// create this [`AttachmentVtable`].
51    type_name: fn() -> &'static str,
52    /// Gets the [`TypeId`] of the handler that was used to create this
53    /// [`AttachmentVtable`].
54    handler_type_id: fn() -> TypeId,
55    /// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this
56    /// pointer.
57    drop: unsafe fn(NonNull<AttachmentData<Erased>>),
58    /// Formats the attachment using the `display` method on the handler.
59    display: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
60    /// Formats the attachment using the `debug` method on the handler.
61    debug: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
62    /// Get the formatting style preferred by the attachment when formatted as
63    /// part of a report.
64    preferred_formatting_style:
65        unsafe fn(RawAttachmentRef<'_>, FormattingFunction) -> AttachmentFormattingStyle,
66    /// Returns a `&dyn Any` view of the attachment.
67    attachment_as_any: unsafe fn(RawAttachmentRef<'_>) -> &(dyn Any + 'static),
68    /// Returns a `&mut dyn Any` view of the attachment.
69    attachment_as_any_mut: unsafe fn(RawAttachmentMut<'_>) -> &mut (dyn Any + 'static),
70}
71
72impl AttachmentVtable {
73    /// Creates a new [`AttachmentVtable`] for the attachment type `A` and the
74    /// handler type `H`.
75    pub(super) const fn new<A: 'static, H: AttachmentHandler<A>>() -> &'static Self {
76        const {
77            &Self {
78                type_id: TypeId::of::<A>,
79                type_name: any::type_name::<A>,
80                handler_type_id: TypeId::of::<H>,
81                drop: drop::<A>,
82                display: display::<A, H>,
83                debug: debug::<A, H>,
84                preferred_formatting_style: preferred_formatting_style::<A, H>,
85                attachment_as_any: attachment_as_any::<A>,
86                attachment_as_any_mut: attachment_as_any_mut::<A>,
87            }
88        }
89    }
90
91    /// Gets the [`TypeId`] of the attachment type that was used to create this
92    /// [`AttachmentVtable`].
93    #[inline]
94    pub(super) fn type_id(&self) -> TypeId {
95        (self.type_id)()
96    }
97
98    /// Gets the [`any::type_name`] of the attachment type that was used to
99    /// create this [`AttachmentVtable`].
100    #[inline]
101    pub(super) fn type_name(&self) -> &'static str {
102        (self.type_name)()
103    }
104
105    /// Gets the [`TypeId`] of the handler that was used to create this
106    /// [`AttachmentVtable`].
107    #[inline]
108    pub(super) fn handler_type_id(&self) -> TypeId {
109        (self.handler_type_id)()
110    }
111
112    /// Drops the `Box<AttachmentData<A>>` instance pointed to by this pointer.
113    ///
114    /// # Safety
115    ///
116    /// The caller must ensure:
117    ///
118    /// 1. The pointer comes from [`Box<AttachmentData<A>>`] via
119    ///    [`Box::into_raw`]
120    /// 2. This [`AttachmentVtable`] must be a vtable for the attachment type
121    ///    stored in the [`AttachmentData`].
122    /// 3. This method drops the [`Box<AttachmentData<A>>`], so the caller must
123    ///    ensure that the pointer has not previously been dropped, that it is
124    ///    able to transfer ownership of the pointer, and that it will not use
125    ///    the pointer after calling this method.
126    #[inline]
127    pub(super) unsafe fn drop(&self, ptr: NonNull<AttachmentData<Erased>>) {
128        // SAFETY: We know that `self.drop` points to the function `drop::<A>` below.
129        // That function's safety requirements are upheld:
130        // 1. Guaranteed by the caller
131        // 2. Guaranteed by the caller
132        // 3. Guaranteed by the caller
133        unsafe {
134            // @add-unsafe-context: drop
135            (self.drop)(ptr);
136        }
137    }
138
139    /// Formats the attachment using the [`H::display`] function
140    /// used when creating this [`AttachmentVtable`].
141    ///
142    /// [`H::display`]: AttachmentHandler::display
143    ///
144    /// # Safety
145    ///
146    /// The caller must ensure:
147    ///
148    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
149    ///    stored in the [`RawAttachmentRef`].
150    #[inline]
151    pub(super) unsafe fn display(
152        &self,
153        ptr: RawAttachmentRef<'_>,
154        formatter: &mut core::fmt::Formatter<'_>,
155    ) -> core::fmt::Result {
156        // SAFETY: We know that the `self.display` field points to the function
157        // `display::<A, H>` below. That function's safety requirements are upheld:
158        // 1. Guaranteed by the caller
159        unsafe {
160            // @add-unsafe-context: display
161            (self.display)(ptr, formatter)
162        }
163    }
164
165    /// Formats the attachment using the [`H::debug`] function
166    /// used when creating this [`AttachmentVtable`].
167    ///
168    /// [`H::debug`]: AttachmentHandler::debug
169    ///
170    /// # Safety
171    ///
172    /// The caller must ensure:
173    ///
174    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
175    ///    stored in the [`RawAttachmentRef`].
176    #[inline]
177    pub(super) unsafe fn debug(
178        &self,
179        ptr: RawAttachmentRef<'_>,
180        formatter: &mut core::fmt::Formatter<'_>,
181    ) -> core::fmt::Result {
182        // SAFETY: We know that the `self.debug` field points to the function
183        // `debug::<A, H>` below. That function's safety requirements are upheld:
184        // 1. Guaranteed by the caller
185        unsafe {
186            // @add-unsafe-context: debug
187            // @add-unsafe-context: RawAttachmentRef
188            // @add-unsafe-context: AttachmentData
189            (self.debug)(ptr, formatter)
190        }
191    }
192
193    /// Gets the preferred formatting style using the
194    /// [`H::preferred_formatting_style`] function used when creating this
195    /// [`AttachmentVtable`].
196    ///
197    /// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
198    ///
199    /// # Safety
200    ///
201    /// The caller must ensure:
202    ///
203    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
204    ///    stored in the [`RawAttachmentRef`].
205    #[inline]
206    pub(super) unsafe fn preferred_formatting_style(
207        &self,
208        ptr: RawAttachmentRef<'_>,
209        report_formatting_function: FormattingFunction,
210    ) -> AttachmentFormattingStyle {
211        // SAFETY: We know that the `self.preferred_formatting_style` field points to
212        // the function `preferred_formatting_style::<A, H>` below. That
213        // function's safety requirements are upheld:
214        // 1. Guaranteed by the caller
215        unsafe {
216            // @add-unsafe-context: preferred_formatting_style
217            // @add-unsafe-context: RawAttachmentRef
218            // @add-unsafe-context: AttachmentData
219            (self.preferred_formatting_style)(ptr, report_formatting_function)
220        }
221    }
222
223    /// Returns a `&dyn Any` reference to the attachment using the function
224    /// pointer stored in this [`AttachmentVtable`].
225    ///
226    /// # Safety
227    ///
228    /// The caller must ensure:
229    ///
230    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
231    ///    stored in the [`RawAttachmentRef`].
232    #[inline]
233    pub(super) unsafe fn attachment_as_any<'a>(
234        &self,
235        ptr: RawAttachmentRef<'a>,
236    ) -> &'a (dyn Any + 'static) {
237        // SAFETY: We know that the `self.attachment_as_any` field points to the
238        // function `attachment_as_any::<A>` below. That function's safety
239        // requirements are upheld:
240        // 1. Guaranteed by the caller
241        unsafe {
242            // @add-unsafe-context: attachment_as_any
243            // @add-unsafe-context: RawAttachmentRef
244            // @add-unsafe-context: AttachmentData
245            (self.attachment_as_any)(ptr)
246        }
247    }
248
249    /// Returns a `&mut dyn Any` reference to the attachment using the function
250    /// pointer stored in this [`AttachmentVtable`].
251    ///
252    /// # Safety
253    ///
254    /// The caller must ensure:
255    ///
256    /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
257    ///    stored in the [`RawAttachmentMut`].
258    #[inline]
259    pub(super) unsafe fn attachment_as_any_mut<'a>(
260        &self,
261        ptr: RawAttachmentMut<'a>,
262    ) -> &'a mut (dyn Any + 'static) {
263        // SAFETY: We know that the `self.attachment_as_any_mut` field points to
264        // the function `attachment_as_any_mut::<A>` below. That function's
265        // safety requirements are upheld:
266        // 1. Guaranteed by the caller
267        unsafe {
268            // @add-unsafe-context: attachment_as_any_mut
269            // @add-unsafe-context: RawAttachmentMut
270            // @add-unsafe-context: AttachmentData
271            (self.attachment_as_any_mut)(ptr)
272        }
273    }
274}
275
276/// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this pointer.
277///
278/// # Safety
279///
280/// The caller must ensure:
281///
282/// 1. The pointer comes from [`Box<AttachmentData<A>>`] via [`Box::into_raw`]
283/// 2. The attachment type `A` matches the actual attachment type stored in the
284///    [`AttachmentData`]
285/// 3. This method drops the [`Box<AttachmentData<A>>`], so the caller must
286///    ensure that the pointer has not previously been dropped, that it is able
287///    to transfer ownership of the pointer, and that it will not use the
288///    pointer after calling this method.
289unsafe fn drop<A: 'static>(ptr: NonNull<AttachmentData<Erased>>) {
290    let ptr: NonNull<AttachmentData<A>> = ptr.cast();
291    let ptr = ptr.as_ptr();
292    // SAFETY: Our pointer has the correct type as guaranteed by the caller, and it
293    // came from a call to `Box::into_raw` as also guaranteed by our caller.
294    let boxed = unsafe {
295        // @add-unsafe-context: AttachmentData
296        Box::from_raw(ptr)
297    };
298    core::mem::drop(boxed);
299}
300
301/// Formats an attachment using its handler's display implementation.
302///
303/// # Safety
304///
305/// The caller must ensure:
306///
307/// 1. The type `A` matches the actual attachment type stored in the
308///    [`AttachmentData`]
309unsafe fn display<A: 'static, H: AttachmentHandler<A>>(
310    ptr: RawAttachmentRef<'_>,
311    formatter: &mut core::fmt::Formatter<'_>,
312) -> core::fmt::Result {
313    // SAFETY:
314    // 1. Guaranteed by the caller
315    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
316    H::display(attachment, formatter)
317}
318
319/// Formats an attachment using its handler's debug implementation.
320///
321/// # Safety
322///
323/// The caller must ensure:
324///
325/// 1. The type `A` matches the actual attachment type stored in the
326///    [`AttachmentData`]
327unsafe fn debug<A: 'static, H: AttachmentHandler<A>>(
328    ptr: RawAttachmentRef<'_>,
329    formatter: &mut core::fmt::Formatter<'_>,
330) -> core::fmt::Result {
331    // SAFETY:
332    // 1. Guaranteed by the caller
333    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
334    H::debug(attachment, formatter)
335}
336
337/// Gets the preferred formatting style using the
338/// [`H::preferred_formatting_style`] function.
339///
340/// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
341///
342/// # Safety
343///
344/// The caller must ensure:
345///
346/// 1. The type `A` matches the actual attachment type stored in the
347///    [`AttachmentData`]
348unsafe fn preferred_formatting_style<A: 'static, H: AttachmentHandler<A>>(
349    ptr: RawAttachmentRef<'_>,
350    report_formatting_function: FormattingFunction,
351) -> AttachmentFormattingStyle {
352    // SAFETY:
353    // 1. Guaranteed by the caller
354    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
355    H::preferred_formatting_style(attachment, report_formatting_function)
356}
357
358/// Returns a `&dyn Any` view of the attachment.
359///
360/// # Safety
361///
362/// The caller must ensure:
363///
364/// 1. The type `A` matches the actual attachment type stored in the
365///    [`AttachmentData`]
366unsafe fn attachment_as_any<'a, A: 'static>(ptr: RawAttachmentRef<'a>) -> &'a (dyn Any + 'static) {
367    // SAFETY:
368    // 1. Guaranteed by the caller
369    let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
370    attachment as &(dyn Any + 'static)
371}
372
373/// Returns a `&mut dyn Any` view of the attachment.
374///
375/// # Safety
376///
377/// The caller must ensure:
378///
379/// 1. The type `A` matches the actual attachment type stored in the
380///    [`AttachmentData`]
381unsafe fn attachment_as_any_mut<'a, A: 'static>(
382    ptr: RawAttachmentMut<'a>,
383) -> &'a mut (dyn Any + 'static) {
384    // SAFETY:
385    // 1. Guaranteed by the caller
386    let attachment: &mut A = unsafe { ptr.into_attachment_downcast_unchecked::<A>() };
387    attachment as &mut (dyn Any + 'static)
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393    use crate::handlers::AttachmentHandler;
394
395    struct HandlerI32;
396    impl AttachmentHandler<i32> for HandlerI32 {
397        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398            core::fmt::Display::fmt(value, formatter)
399        }
400
401        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
402            core::fmt::Debug::fmt(value, formatter)
403        }
404    }
405
406    #[test]
407    fn test_attachment_vtable_eq() {
408        // Test that vtables have proper static lifetime and can be safely shared
409        let vtable1 = AttachmentVtable::new::<i32, HandlerI32>();
410        let vtable2 = AttachmentVtable::new::<i32, HandlerI32>();
411
412        // Both should be the exact same static instance
413        assert!(core::ptr::eq(vtable1, vtable2));
414    }
415
416    #[test]
417    fn test_attachment_type_id() {
418        let vtable = AttachmentVtable::new::<i32, HandlerI32>();
419        assert_eq!(vtable.type_id(), TypeId::of::<i32>());
420    }
421
422    #[test]
423    fn test_attachment_type_name() {
424        let vtable = AttachmentVtable::new::<i32, HandlerI32>();
425        assert_eq!(vtable.type_name(), core::any::type_name::<i32>());
426    }
427}