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
12//! modified after construction (no `pub` or `pub(crate)` fields), the types
13//! remain in 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
20//! reference 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 ///
36 /// # Safety
37 ///
38 /// The following safety invariants are guaranteed to be upheld as long as
39 /// this struct exists:
40 ///
41 /// 1. The vtable must always point to an `AttachmentVtable` created for the
42 /// actual attachment type `A` stored below. This is true even when
43 /// accessed via type-erased pointers.
44 vtable: &'static AttachmentVtable,
45 /// The actual attachment data
46 attachment: A,
47}
48
49impl<A: 'static> AttachmentData<A> {
50 /// Creates a new [`AttachmentData`] with the specified handler and
51 /// attachment.
52 ///
53 /// This method creates the vtable for type-erased dispatch and pairs it
54 /// with the attachment data.
55 #[inline]
56 pub(super) fn new<H: AttachmentHandler<A>>(attachment: A) -> Self {
57 Self {
58 vtable: AttachmentVtable::new::<A, H>(),
59 attachment,
60 }
61 }
62}
63
64impl<'a> RawAttachmentRef<'a> {
65 /// Returns a reference to the [`AttachmentVtable`] of the
66 /// [`AttachmentData`] instance.
67 ///
68 /// The returned vtable is guaranteed to be a vtable for the
69 /// attachment type stored in the [`AttachmentData`].
70 #[inline]
71 pub(super) fn vtable(self) -> &'static AttachmentVtable {
72 let ptr = self.as_ptr();
73 // SAFETY: The safety requirements for `&raw const (*ptr).vtable` are upheld:
74 // 1. `ptr` is a valid pointer to a live `AttachmentData<A>` (for some `A`) as
75 // guaranteed by `RawAttachmentRef`'s invariants
76 // 2. `AttachmentData<A>` is `#[repr(C)]`, so the `vtable` field is at a
77 // consistent offset regardless of the type parameter `A`
78 // 3. We avoid creating a reference to the full `AttachmentData` struct, which
79 // would be UB since we don't know the correct type parameter
80 let vtable_ptr: *const &'static AttachmentVtable = unsafe {
81 // @add-unsafe-context: AttachmentData
82 &raw const (*ptr).vtable
83 };
84
85 // SAFETY: The safety requirements for dereferencing `vtable_ptr` are upheld:
86 // 1. The pointer is valid and properly aligned because it points to the first
87 // field of a valid `AttachmentData<A>` instance
88 // 2. The `vtable` field is initialized in `AttachmentData::new` and never
89 // modified, so it contains a valid `&'static AttachmentVtable` value
90 unsafe { *vtable_ptr }
91 }
92
93 /// Accesses the inner attachment of the [`AttachmentData`] instance as a
94 /// reference to the specified type.
95 ///
96 /// # Safety
97 ///
98 /// The caller must ensure:
99 ///
100 /// 1. The type `A` matches the actual attachment type stored in the
101 /// [`AttachmentData`].
102 #[inline]
103 pub unsafe fn attachment_downcast_unchecked<A: 'static>(self) -> &'a A {
104 // SAFETY:
105 // 1. Guaranteed by the caller
106 let this = unsafe {
107 // @add-unsafe-context: AttachmentData
108 self.cast_inner::<A>()
109 };
110 &this.attachment
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_attachment_field_offsets() {
120 use core::mem::{offset_of, size_of};
121
122 #[repr(align(32))]
123 struct LargeAlignment {
124 _value: u8,
125 }
126
127 assert_eq!(offset_of!(AttachmentData<u8>, vtable), 0);
128 assert_eq!(offset_of!(AttachmentData<u32>, vtable), 0);
129 assert_eq!(offset_of!(AttachmentData<[u64; 4]>, vtable), 0);
130 assert_eq!(offset_of!(AttachmentData<LargeAlignment>, vtable), 0);
131
132 assert!(
133 offset_of!(AttachmentData<u8>, attachment) >= size_of::<&'static AttachmentVtable>()
134 );
135 assert!(
136 offset_of!(AttachmentData<u32>, attachment) >= size_of::<&'static AttachmentVtable>()
137 );
138 assert!(
139 offset_of!(AttachmentData<[u64; 4]>, attachment)
140 >= size_of::<&'static AttachmentVtable>()
141 );
142 assert!(
143 offset_of!(AttachmentData<LargeAlignment>, attachment)
144 >= size_of::<&'static AttachmentVtable>()
145 );
146 }
147}