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}