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}