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 modified
12//! after construction (no `pub` or `pub(crate)` fields), the types remain in
13//! 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 reference
20//! 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    vtable: &'static AttachmentVtable,
36    /// The actual attachment data
37    attachment: A,
38}
39
40impl<A: 'static> AttachmentData<A> {
41    /// Creates a new [`AttachmentData`] with the specified handler and
42    /// attachment.
43    ///
44    /// This method creates the vtable for type-erased dispatch and pairs it
45    /// with the attachment data.
46    #[inline]
47    pub(super) fn new<H: AttachmentHandler<A>>(attachment: A) -> Self {
48        Self {
49            vtable: AttachmentVtable::new::<A, H>(),
50            attachment,
51        }
52    }
53}
54
55impl<'a> RawAttachmentRef<'a> {
56    /// Returns a reference to the [`AttachmentVtable`] of the
57    /// [`AttachmentData`] instance.
58    #[inline]
59    pub(super) fn vtable(self) -> &'static AttachmentVtable {
60        let ptr = self.as_ptr();
61        // SAFETY: We don't know the actual inner attachment type, but we do know
62        // that it points to an instance of `AttachmentData<A>` for some specific `A`.
63        // Since `AttachmentData<A>` is `#[repr(C)]`, that means that it's
64        // safe to create pointers to the fields before the actual attachment.
65        //
66        // We need to take care to avoid creating an actual reference to
67        // the `AttachmentData` itself though, as that would still be undefined behavior
68        // since we don't have the right type.
69        let vtable_ptr: *const &'static AttachmentVtable = unsafe { &raw const (*ptr).vtable };
70
71        // SAFETY: The vtable_ptr is derived from a valid Box pointer and points
72        // to an initialized `&'static AttachmentVtable` field. Dereferencing is safe
73        // because:
74        // - The pointer is valid and properly aligned
75        // - The vtable field is initialized in AttachmentData::new and never modified
76        unsafe { *vtable_ptr }
77    }
78
79    /// Accesses the inner attachment of the [`AttachmentData`] instance as a
80    /// reference to the specified type.
81    ///
82    /// # Safety
83    ///
84    /// The caller must ensure that the type `A` matches the actual attachment
85    /// type stored in the [`AttachmentData`].
86    #[inline]
87    pub unsafe fn attachment_downcast_unchecked<A: 'static>(self) -> &'a A {
88        // SAFETY: The inner function requires that `A` matches the type stored, but
89        // that is guaranteed by our caller.
90        let this = unsafe { self.cast_inner::<A>() };
91        &this.attachment
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_attachment_field_offsets() {
101        use core::mem::{offset_of, size_of};
102
103        #[repr(align(32))]
104        struct LargeAlignment {
105            _value: u8,
106        }
107
108        assert_eq!(offset_of!(AttachmentData<u8>, vtable), 0);
109        assert_eq!(offset_of!(AttachmentData<u32>, vtable), 0);
110        assert_eq!(offset_of!(AttachmentData<[u64; 4]>, vtable), 0);
111        assert_eq!(offset_of!(AttachmentData<LargeAlignment>, vtable), 0);
112
113        assert!(
114            offset_of!(AttachmentData<u8>, attachment) >= size_of::<&'static AttachmentVtable>()
115        );
116        assert!(
117            offset_of!(AttachmentData<u32>, attachment) >= size_of::<&'static AttachmentVtable>()
118        );
119        assert!(
120            offset_of!(AttachmentData<[u64; 4]>, attachment)
121                >= size_of::<&'static AttachmentVtable>()
122        );
123        assert!(
124            offset_of!(AttachmentData<LargeAlignment>, attachment)
125                >= size_of::<&'static AttachmentVtable>()
126        );
127    }
128}