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}