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}