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