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) -> 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(
76 &self,
77 ptr: RawAttachmentRef<'_>,
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(
96 &self,
97 ptr: RawAttachmentRef<'_>,
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(
116 &self,
117 ptr: RawAttachmentRef<'_>,
118 report_formatting_function: FormattingFunction,
119 ) -> AttachmentFormattingStyle {
120 // SAFETY: We know that the `self.preferred_formatting_style` field points to the function `preferred_formatting_style::<A, H>` below.
121 // That function requires that the attachment type `A` matches the actual attachment type stored in the `AttachmentData`,
122 // which is guaranteed by our caller.
123 unsafe { (self.preferred_formatting_style)(ptr, report_formatting_function) }
124 }
125}
126
127/// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this pointer.
128///
129/// # Safety
130///
131/// - The caller must ensure that the pointer comes from a [`Box<AttachmentData<A>>`], which was turned
132/// into a pointer using [`Box::into_raw`].
133/// - The attachment type `A` must match the actual attachment type stored in the [`AttachmentData`].
134/// - After calling this method, the pointer must no longer be used.
135unsafe fn drop<A: 'static>(ptr: NonNull<AttachmentData<Erased>>) {
136 let ptr: NonNull<AttachmentData<A>> = ptr.cast();
137 let ptr = ptr.as_ptr();
138 // SAFETY: Our pointer has the correct type as guaranteed by the caller, and it came
139 // from a call to [`Box::into_raw`] as also guaranteed by our caller.
140 let boxed = unsafe { Box::from_raw(ptr) };
141 core::mem::drop(boxed);
142}
143
144/// Formats an attachment using its handler's display implementation.
145///
146/// # Safety
147///
148/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
149unsafe fn display<A: 'static, H: AttachmentHandler<A>>(
150 ptr: RawAttachmentRef<'_>,
151 formatter: &mut core::fmt::Formatter<'_>,
152) -> core::fmt::Result {
153 // SAFETY: Our caller guarantees that the type `A` matches the actual attachment type stored in the `AttachmentData`.
154 let context: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
155 H::display(context, formatter)
156}
157
158/// Formats an attachment using its handler's debug implementation.
159///
160/// # Safety
161///
162/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
163unsafe fn debug<A: 'static, H: AttachmentHandler<A>>(
164 ptr: RawAttachmentRef<'_>,
165 formatter: &mut core::fmt::Formatter<'_>,
166) -> core::fmt::Result {
167 // SAFETY: Our caller guarantees that the type `A` matches the actual attachment type stored in the `AttachmentData`.
168 let context: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
169 H::debug(context, formatter)
170}
171
172/// Gets the preferred formatting style using the [`H::preferred_formatting_style`] function.
173///
174/// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
175///
176/// # Safety
177///
178/// The caller must ensure that the type `A` matches the actual attachment type stored in the [`AttachmentData`].
179unsafe fn preferred_formatting_style<A: 'static, H: AttachmentHandler<A>>(
180 ptr: RawAttachmentRef<'_>,
181 report_formatting_function: FormattingFunction,
182) -> AttachmentFormattingStyle {
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::preferred_formatting_style(context, report_formatting_function)
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::handlers::AttachmentHandler;
192
193 struct HandlerI32;
194 impl AttachmentHandler<i32> for HandlerI32 {
195 fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
196 core::fmt::Display::fmt(value, formatter)
197 }
198
199 fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
200 core::fmt::Debug::fmt(value, formatter)
201 }
202 }
203
204 #[test]
205 fn test_attachment_vtable_eq() {
206 // Test that vtables have proper static lifetime and can be safely shared
207 let vtable1 = AttachmentVtable::new::<i32, HandlerI32>();
208 let vtable2 = AttachmentVtable::new::<i32, HandlerI32>();
209
210 // Both should be the exact same static instance
211 assert!(core::ptr::eq(vtable1, vtable2));
212 }
213
214 #[test]
215 fn test_attachment_type_id() {
216 let vtable = AttachmentVtable::new::<i32, HandlerI32>();
217 assert_eq!(vtable.type_id(), TypeId::of::<i32>());
218 }
219}