rootcause_internals/attachment/data.rs
1//! `AttachmentData<A>` wrapper and field access.
2//!
3//! This module encapsulates the fields of [`AttachmentData`], ensuring they are
4//! only visible within this module. This visibility restriction guarantees the
5//! safety invariant: **the vtable type must always match the actual attachment
6//! type**.
7//!
8//! # Safety Invariant
9//!
10//! Since `AttachmentData` can only be constructed via [`AttachmentData::new`]
11//! (which creates matching vtable and attachment), and fields cannot be modified
12//! after construction (no `pub` or `pub(crate)` fields), the types remain in
13//! sync throughout the value's lifetime.
14//!
15//! # `#[repr(C)]` Layout
16//!
17//! The `#[repr(C)]` attribute enables safe field projection even when the type
18//! parameter `A` is erased. This allows accessing the vtable field from a
19//! pointer to `AttachmentData<Erased>` without constructing an invalid reference
20//! to the full struct.
21
22use crate::{
23 attachment::{raw::RawAttachmentRef, vtable::AttachmentVtable},
24 handlers::AttachmentHandler,
25};
26
27/// Type-erased attachment data structure with vtable-based dispatch.
28///
29/// This struct uses `#[repr(C)]` to enable safe field access in type-erased
30/// contexts, allowing access to the vtable field even when the concrete
31/// attachment type `A` is unknown.
32#[repr(C)]
33pub(crate) struct AttachmentData<A: 'static> {
34 /// The Vtable of this attachment
35 vtable: &'static AttachmentVtable,
36 /// The actual attachment data
37 attachment: A,
38}
39
40impl<A: 'static> AttachmentData<A> {
41 /// Creates a new [`AttachmentData`] with the specified handler and
42 /// attachment.
43 ///
44 /// This method creates the vtable for type-erased dispatch and pairs it
45 /// with the attachment data.
46 #[inline]
47 pub(super) fn new<H: AttachmentHandler<A>>(attachment: A) -> Self {
48 Self {
49 vtable: AttachmentVtable::new::<A, H>(),
50 attachment,
51 }
52 }
53}
54
55impl<'a> RawAttachmentRef<'a> {
56 /// Returns a reference to the [`AttachmentVtable`] of the
57 /// [`AttachmentData`] instance.
58 #[inline]
59 pub(super) fn vtable(self) -> &'static AttachmentVtable {
60 let ptr = self.as_ptr();
61 // SAFETY: We don't know the actual inner attachment type, but we do know
62 // that it points to an instance of `AttachmentData<A>` for some specific `A`.
63 // Since `AttachmentData<A>` is `#[repr(C)]`, that means that it's
64 // safe to create pointers to the fields before the actual attachment.
65 //
66 // We need to take care to avoid creating an actual reference to
67 // the `AttachmentData` itself though, as that would still be undefined behavior
68 // since we don't have the right type.
69 let vtable_ptr: *const &'static AttachmentVtable = unsafe { &raw const (*ptr).vtable };
70
71 // SAFETY: The vtable_ptr is derived from a valid Box pointer and points
72 // to an initialized `&'static AttachmentVtable` field. Dereferencing is safe
73 // because:
74 // - The pointer is valid and properly aligned
75 // - The vtable field is initialized in AttachmentData::new and never modified
76 unsafe { *vtable_ptr }
77 }
78
79 /// Accesses the inner attachment of the [`AttachmentData`] instance as a
80 /// reference to the specified type.
81 ///
82 /// # Safety
83 ///
84 /// The caller must ensure that the type `A` matches the actual attachment
85 /// type stored in the [`AttachmentData`].
86 #[inline]
87 pub unsafe fn attachment_downcast_unchecked<A: 'static>(self) -> &'a A {
88 // SAFETY: The inner function requires that `A` matches the type stored, but
89 // that is guaranteed by our caller.
90 let this = unsafe { self.cast_inner::<A>() };
91 &this.attachment
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn test_attachment_field_offsets() {
101 use core::mem::{offset_of, size_of};
102
103 #[repr(align(32))]
104 struct LargeAlignment {
105 _value: u8,
106 }
107
108 assert_eq!(offset_of!(AttachmentData<u8>, vtable), 0);
109 assert_eq!(offset_of!(AttachmentData<u32>, vtable), 0);
110 assert_eq!(offset_of!(AttachmentData<[u64; 4]>, vtable), 0);
111 assert_eq!(offset_of!(AttachmentData<LargeAlignment>, vtable), 0);
112
113 assert!(
114 offset_of!(AttachmentData<u8>, attachment) >= size_of::<&'static AttachmentVtable>()
115 );
116 assert!(
117 offset_of!(AttachmentData<u32>, attachment) >= size_of::<&'static AttachmentVtable>()
118 );
119 assert!(
120 offset_of!(AttachmentData<[u64; 4]>, attachment)
121 >= size_of::<&'static AttachmentVtable>()
122 );
123 assert!(
124 offset_of!(AttachmentData<LargeAlignment>, attachment)
125 >= size_of::<&'static AttachmentVtable>()
126 );
127 }
128}