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::{
21 any::{self, Any, TypeId},
22 ptr::NonNull,
23};
24
25use crate::{
26 attachment::{
27 data::AttachmentData,
28 raw::{RawAttachmentMut, RawAttachmentRef},
29 },
30 handlers::{AttachmentFormattingStyle, AttachmentHandler, FormattingFunction},
31 util::Erased,
32};
33
34/// Vtable for type-erased attachment operations.
35///
36/// Contains function pointers for performing operations on attachments without
37/// knowing their concrete type at compile time.
38///
39/// # Safety Invariant
40///
41/// The fields `drop`, `display`, `debug`, and `preferred_formatting_style` are
42/// guaranteed to point to the functions defined below instantiated with the
43/// attachment type `A` and handler type `H` that were used to create this
44/// [`AttachmentVtable`].
45pub(crate) struct AttachmentVtable {
46 /// Gets the [`TypeId`] of the attachment type that was used to create this
47 /// [`AttachmentVtable`].
48 type_id: fn() -> TypeId,
49 /// Gets the [`any::type_name`] of the attachment type that was used to
50 /// create this [`AttachmentVtable`].
51 type_name: fn() -> &'static str,
52 /// Gets the [`TypeId`] of the handler that was used to create this
53 /// [`AttachmentVtable`].
54 handler_type_id: fn() -> TypeId,
55 /// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this
56 /// pointer.
57 drop: unsafe fn(NonNull<AttachmentData<Erased>>),
58 /// Formats the attachment using the `display` method on the handler.
59 display: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
60 /// Formats the attachment using the `debug` method on the handler.
61 debug: unsafe fn(RawAttachmentRef<'_>, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
62 /// Get the formatting style preferred by the attachment when formatted as
63 /// part of a report.
64 preferred_formatting_style:
65 unsafe fn(RawAttachmentRef<'_>, FormattingFunction) -> AttachmentFormattingStyle,
66 /// Returns a `&dyn Any` view of the attachment.
67 attachment_as_any: unsafe fn(RawAttachmentRef<'_>) -> &(dyn Any + 'static),
68 /// Returns a `&mut dyn Any` view of the attachment.
69 attachment_as_any_mut: unsafe fn(RawAttachmentMut<'_>) -> &mut (dyn Any + 'static),
70}
71
72impl AttachmentVtable {
73 /// Creates a new [`AttachmentVtable`] for the attachment type `A` and the
74 /// handler type `H`.
75 pub(super) const fn new<A: 'static, H: AttachmentHandler<A>>() -> &'static Self {
76 const {
77 &Self {
78 type_id: TypeId::of::<A>,
79 type_name: any::type_name::<A>,
80 handler_type_id: TypeId::of::<H>,
81 drop: drop::<A>,
82 display: display::<A, H>,
83 debug: debug::<A, H>,
84 preferred_formatting_style: preferred_formatting_style::<A, H>,
85 attachment_as_any: attachment_as_any::<A>,
86 attachment_as_any_mut: attachment_as_any_mut::<A>,
87 }
88 }
89 }
90
91 /// Gets the [`TypeId`] of the attachment type that was used to create this
92 /// [`AttachmentVtable`].
93 #[inline]
94 pub(super) fn type_id(&self) -> TypeId {
95 (self.type_id)()
96 }
97
98 /// Gets the [`any::type_name`] of the attachment type that was used to
99 /// create this [`AttachmentVtable`].
100 #[inline]
101 pub(super) fn type_name(&self) -> &'static str {
102 (self.type_name)()
103 }
104
105 /// Gets the [`TypeId`] of the handler that was used to create this
106 /// [`AttachmentVtable`].
107 #[inline]
108 pub(super) fn handler_type_id(&self) -> TypeId {
109 (self.handler_type_id)()
110 }
111
112 /// Drops the `Box<AttachmentData<A>>` instance pointed to by this pointer.
113 ///
114 /// # Safety
115 ///
116 /// The caller must ensure:
117 ///
118 /// 1. The pointer comes from [`Box<AttachmentData<A>>`] via
119 /// [`Box::into_raw`]
120 /// 2. This [`AttachmentVtable`] must be a vtable for the attachment type
121 /// stored in the [`AttachmentData`].
122 /// 3. This method drops the [`Box<AttachmentData<A>>`], so the caller must
123 /// ensure that the pointer has not previously been dropped, that it is
124 /// able to transfer ownership of the pointer, and that it will not use
125 /// the pointer after calling this method.
126 #[inline]
127 pub(super) unsafe fn drop(&self, ptr: NonNull<AttachmentData<Erased>>) {
128 // SAFETY: We know that `self.drop` points to the function `drop::<A>` below.
129 // That function's safety requirements are upheld:
130 // 1. Guaranteed by the caller
131 // 2. Guaranteed by the caller
132 // 3. Guaranteed by the caller
133 unsafe {
134 // @add-unsafe-context: drop
135 (self.drop)(ptr);
136 }
137 }
138
139 /// Formats the attachment using the [`H::display`] function
140 /// used when creating this [`AttachmentVtable`].
141 ///
142 /// [`H::display`]: AttachmentHandler::display
143 ///
144 /// # Safety
145 ///
146 /// The caller must ensure:
147 ///
148 /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
149 /// stored in the [`RawAttachmentRef`].
150 #[inline]
151 pub(super) unsafe fn display(
152 &self,
153 ptr: RawAttachmentRef<'_>,
154 formatter: &mut core::fmt::Formatter<'_>,
155 ) -> core::fmt::Result {
156 // SAFETY: We know that the `self.display` field points to the function
157 // `display::<A, H>` below. That function's safety requirements are upheld:
158 // 1. Guaranteed by the caller
159 unsafe {
160 // @add-unsafe-context: display
161 (self.display)(ptr, formatter)
162 }
163 }
164
165 /// Formats the attachment using the [`H::debug`] function
166 /// used when creating this [`AttachmentVtable`].
167 ///
168 /// [`H::debug`]: AttachmentHandler::debug
169 ///
170 /// # Safety
171 ///
172 /// The caller must ensure:
173 ///
174 /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
175 /// stored in the [`RawAttachmentRef`].
176 #[inline]
177 pub(super) unsafe fn debug(
178 &self,
179 ptr: RawAttachmentRef<'_>,
180 formatter: &mut core::fmt::Formatter<'_>,
181 ) -> core::fmt::Result {
182 // SAFETY: We know that the `self.debug` field points to the function
183 // `debug::<A, H>` below. That function's safety requirements are upheld:
184 // 1. Guaranteed by the caller
185 unsafe {
186 // @add-unsafe-context: debug
187 // @add-unsafe-context: RawAttachmentRef
188 // @add-unsafe-context: AttachmentData
189 (self.debug)(ptr, formatter)
190 }
191 }
192
193 /// Gets the preferred formatting style using the
194 /// [`H::preferred_formatting_style`] function used when creating this
195 /// [`AttachmentVtable`].
196 ///
197 /// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
198 ///
199 /// # Safety
200 ///
201 /// The caller must ensure:
202 ///
203 /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
204 /// stored in the [`RawAttachmentRef`].
205 #[inline]
206 pub(super) unsafe fn preferred_formatting_style(
207 &self,
208 ptr: RawAttachmentRef<'_>,
209 report_formatting_function: FormattingFunction,
210 ) -> AttachmentFormattingStyle {
211 // SAFETY: We know that the `self.preferred_formatting_style` field points to
212 // the function `preferred_formatting_style::<A, H>` below. That
213 // function's safety requirements are upheld:
214 // 1. Guaranteed by the caller
215 unsafe {
216 // @add-unsafe-context: preferred_formatting_style
217 // @add-unsafe-context: RawAttachmentRef
218 // @add-unsafe-context: AttachmentData
219 (self.preferred_formatting_style)(ptr, report_formatting_function)
220 }
221 }
222
223 /// Returns a `&dyn Any` reference to the attachment using the function
224 /// pointer stored in this [`AttachmentVtable`].
225 ///
226 /// # Safety
227 ///
228 /// The caller must ensure:
229 ///
230 /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
231 /// stored in the [`RawAttachmentRef`].
232 #[inline]
233 pub(super) unsafe fn attachment_as_any<'a>(
234 &self,
235 ptr: RawAttachmentRef<'a>,
236 ) -> &'a (dyn Any + 'static) {
237 // SAFETY: We know that the `self.attachment_as_any` field points to the
238 // function `attachment_as_any::<A>` below. That function's safety
239 // requirements are upheld:
240 // 1. Guaranteed by the caller
241 unsafe {
242 // @add-unsafe-context: attachment_as_any
243 // @add-unsafe-context: RawAttachmentRef
244 // @add-unsafe-context: AttachmentData
245 (self.attachment_as_any)(ptr)
246 }
247 }
248
249 /// Returns a `&mut dyn Any` reference to the attachment using the function
250 /// pointer stored in this [`AttachmentVtable`].
251 ///
252 /// # Safety
253 ///
254 /// The caller must ensure:
255 ///
256 /// 1. This [`AttachmentVtable`] must be a vtable for the attachment type
257 /// stored in the [`RawAttachmentMut`].
258 #[inline]
259 pub(super) unsafe fn attachment_as_any_mut<'a>(
260 &self,
261 ptr: RawAttachmentMut<'a>,
262 ) -> &'a mut (dyn Any + 'static) {
263 // SAFETY: We know that the `self.attachment_as_any_mut` field points to
264 // the function `attachment_as_any_mut::<A>` below. That function's
265 // safety requirements are upheld:
266 // 1. Guaranteed by the caller
267 unsafe {
268 // @add-unsafe-context: attachment_as_any_mut
269 // @add-unsafe-context: RawAttachmentMut
270 // @add-unsafe-context: AttachmentData
271 (self.attachment_as_any_mut)(ptr)
272 }
273 }
274}
275
276/// Drops the [`Box<AttachmentData<A>>`] instance pointed to by this pointer.
277///
278/// # Safety
279///
280/// The caller must ensure:
281///
282/// 1. The pointer comes from [`Box<AttachmentData<A>>`] via [`Box::into_raw`]
283/// 2. The attachment type `A` matches the actual attachment type stored in the
284/// [`AttachmentData`]
285/// 3. This method drops the [`Box<AttachmentData<A>>`], so the caller must
286/// ensure that the pointer has not previously been dropped, that it is able
287/// to transfer ownership of the pointer, and that it will not use the
288/// pointer after calling this method.
289unsafe fn drop<A: 'static>(ptr: NonNull<AttachmentData<Erased>>) {
290 let ptr: NonNull<AttachmentData<A>> = ptr.cast();
291 let ptr = ptr.as_ptr();
292 // SAFETY: Our pointer has the correct type as guaranteed by the caller, and it
293 // came from a call to `Box::into_raw` as also guaranteed by our caller.
294 let boxed = unsafe {
295 // @add-unsafe-context: AttachmentData
296 Box::from_raw(ptr)
297 };
298 core::mem::drop(boxed);
299}
300
301/// Formats an attachment using its handler's display implementation.
302///
303/// # Safety
304///
305/// The caller must ensure:
306///
307/// 1. The type `A` matches the actual attachment type stored in the
308/// [`AttachmentData`]
309unsafe fn display<A: 'static, H: AttachmentHandler<A>>(
310 ptr: RawAttachmentRef<'_>,
311 formatter: &mut core::fmt::Formatter<'_>,
312) -> core::fmt::Result {
313 // SAFETY:
314 // 1. Guaranteed by the caller
315 let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
316 H::display(attachment, formatter)
317}
318
319/// Formats an attachment using its handler's debug implementation.
320///
321/// # Safety
322///
323/// The caller must ensure:
324///
325/// 1. The type `A` matches the actual attachment type stored in the
326/// [`AttachmentData`]
327unsafe fn debug<A: 'static, H: AttachmentHandler<A>>(
328 ptr: RawAttachmentRef<'_>,
329 formatter: &mut core::fmt::Formatter<'_>,
330) -> core::fmt::Result {
331 // SAFETY:
332 // 1. Guaranteed by the caller
333 let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
334 H::debug(attachment, formatter)
335}
336
337/// Gets the preferred formatting style using the
338/// [`H::preferred_formatting_style`] function.
339///
340/// [`H::preferred_formatting_style`]: AttachmentHandler::preferred_formatting_style
341///
342/// # Safety
343///
344/// The caller must ensure:
345///
346/// 1. The type `A` matches the actual attachment type stored in the
347/// [`AttachmentData`]
348unsafe fn preferred_formatting_style<A: 'static, H: AttachmentHandler<A>>(
349 ptr: RawAttachmentRef<'_>,
350 report_formatting_function: FormattingFunction,
351) -> AttachmentFormattingStyle {
352 // SAFETY:
353 // 1. Guaranteed by the caller
354 let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
355 H::preferred_formatting_style(attachment, report_formatting_function)
356}
357
358/// Returns a `&dyn Any` view of the attachment.
359///
360/// # Safety
361///
362/// The caller must ensure:
363///
364/// 1. The type `A` matches the actual attachment type stored in the
365/// [`AttachmentData`]
366unsafe fn attachment_as_any<'a, A: 'static>(ptr: RawAttachmentRef<'a>) -> &'a (dyn Any + 'static) {
367 // SAFETY:
368 // 1. Guaranteed by the caller
369 let attachment: &A = unsafe { ptr.attachment_downcast_unchecked::<A>() };
370 attachment as &(dyn Any + 'static)
371}
372
373/// Returns a `&mut dyn Any` view of the attachment.
374///
375/// # Safety
376///
377/// The caller must ensure:
378///
379/// 1. The type `A` matches the actual attachment type stored in the
380/// [`AttachmentData`]
381unsafe fn attachment_as_any_mut<'a, A: 'static>(
382 ptr: RawAttachmentMut<'a>,
383) -> &'a mut (dyn Any + 'static) {
384 // SAFETY:
385 // 1. Guaranteed by the caller
386 let attachment: &mut A = unsafe { ptr.into_attachment_downcast_unchecked::<A>() };
387 attachment as &mut (dyn Any + 'static)
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use crate::handlers::AttachmentHandler;
394
395 struct HandlerI32;
396 impl AttachmentHandler<i32> for HandlerI32 {
397 fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398 core::fmt::Display::fmt(value, formatter)
399 }
400
401 fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
402 core::fmt::Debug::fmt(value, formatter)
403 }
404 }
405
406 #[test]
407 fn test_attachment_vtable_eq() {
408 // Test that vtables have proper static lifetime and can be safely shared
409 let vtable1 = AttachmentVtable::new::<i32, HandlerI32>();
410 let vtable2 = AttachmentVtable::new::<i32, HandlerI32>();
411
412 // Both should be the exact same static instance
413 assert!(core::ptr::eq(vtable1, vtable2));
414 }
415
416 #[test]
417 fn test_attachment_type_id() {
418 let vtable = AttachmentVtable::new::<i32, HandlerI32>();
419 assert_eq!(vtable.type_id(), TypeId::of::<i32>());
420 }
421
422 #[test]
423 fn test_attachment_type_name() {
424 let vtable = AttachmentVtable::new::<i32, HandlerI32>();
425 assert_eq!(vtable.type_name(), core::any::type_name::<i32>());
426 }
427}