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