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}