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 // @add-unsafe-context: crate::util::Erased
83 &raw const (*ptr).vtable
84 };
85
86 // SAFETY: The safety requirements for dereferencing `vtable_ptr` are upheld:
87 // 1. The pointer is valid and properly aligned because it points to the first
88 // field of a valid `AttachmentData<A>` instance
89 // 2. The `vtable` field is initialized in `AttachmentData::new` and never
90 // modified, so it contains a valid `&'static AttachmentVtable` value
91 unsafe { *vtable_ptr }
92 }
93
94 /// Accesses the inner attachment of the [`AttachmentData`] instance as a
95 /// reference to the specified type.
96 ///
97 /// # Safety
98 ///
99 /// The caller must ensure:
100 ///
101 /// 1. The type `A` matches the actual attachment type stored in the
102 /// [`AttachmentData`].
103 #[inline]
104 pub unsafe fn attachment_downcast_unchecked<A: 'static>(self) -> &'a A {
105 // SAFETY:
106 // 1. Guaranteed by the caller
107 let this = unsafe {
108 // @add-unsafe-context: AttachmentData
109 self.cast_inner::<A>()
110 };
111 &this.attachment
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_attachment_field_offsets() {
121 use core::mem::{offset_of, size_of};
122
123 #[repr(align(32))]
124 struct LargeAlignment {
125 _value: u8,
126 }
127
128 assert_eq!(offset_of!(AttachmentData<u8>, vtable), 0);
129 assert_eq!(offset_of!(AttachmentData<u32>, vtable), 0);
130 assert_eq!(offset_of!(AttachmentData<[u64; 4]>, vtable), 0);
131 assert_eq!(offset_of!(AttachmentData<LargeAlignment>, vtable), 0);
132
133 assert!(
134 offset_of!(AttachmentData<u8>, attachment) >= size_of::<&'static AttachmentVtable>()
135 );
136 assert!(
137 offset_of!(AttachmentData<u32>, attachment) >= size_of::<&'static AttachmentVtable>()
138 );
139 assert!(
140 offset_of!(AttachmentData<[u64; 4]>, attachment)
141 >= size_of::<&'static AttachmentVtable>()
142 );
143 assert!(
144 offset_of!(AttachmentData<LargeAlignment>, attachment)
145 >= size_of::<&'static AttachmentVtable>()
146 );
147 }
148}