rootcause_internals/attachment/
raw.rs

1//! Type-erased attachment pointer types.
2//!
3//! This module encapsulates the `ptr` field of [`RawAttachment`] and
4//! [`RawAttachmentRef`], ensuring it is only visible within this module. This
5//! visibility restriction guarantees the safety invariant: **the pointer always
6//! comes from `Box<AttachmentData<A>>`**.
7//!
8//! # Safety Invariant
9//!
10//! Since the `ptr` field can only be set via [`RawAttachment::new`] (which
11//! creates it from `Box::into_raw`), and cannot be modified afterward (no `pub`
12//! or `pub(crate)` fields), the pointer provenance remains valid throughout the
13//! value's lifetime.
14//!
15//! The [`RawAttachment::drop`] implementation relies on this invariant to
16//! safely reconstruct the `Box` and deallocate the memory.
17//!
18//! # Type Erasure
19//!
20//! The concrete type parameter `A` is erased by casting to
21//! `AttachmentData<Erased>`. The vtable stored within the `AttachmentData`
22//! provides the runtime type information needed to safely downcast and format
23//! attachments.
24
25use alloc::boxed::Box;
26use core::{any::TypeId, ptr::NonNull};
27
28use crate::{
29    attachment::data::AttachmentData,
30    handlers::{AttachmentFormattingStyle, AttachmentHandler, FormattingFunction},
31    util::Erased,
32};
33
34/// A pointer to an [`AttachmentData`] that is guaranteed to point to an
35/// initialized instance of an [`AttachmentData<A>`] for some specific `A`,
36/// though we do not know which actual `A` it is.
37///
38/// However, the pointer is allowed to transition into a non-initialized state
39/// inside the [`RawAttachment::drop`] method.
40///
41/// The pointer is guaranteed to have been created using [`Box::into_raw`].
42///
43/// We cannot use a [`Box<AttachmentData<A>>`] directly, because that does not
44/// allow us to type-erase the `A`.
45#[repr(transparent)]
46pub struct RawAttachment {
47    /// Pointer to the inner attachment data
48    ///
49    /// # Safety
50    ///
51    /// The following safety invariants are guaranteed to be upheld as long as
52    /// this struct exists:
53    ///
54    /// 1. The pointer must have been created from a `Box<AttachmentData<A>>`
55    ///    for some `A` using `Box::into_raw`.
56    /// 2. The pointer will point to the same `AttachmentData<A>` for the entire
57    ///    lifetime of this object.
58    /// 3. The pointee is properly initialized for the entire lifetime of this
59    ///    object, except during the execution of the `Drop` implementation.
60    ptr: NonNull<AttachmentData<Erased>>,
61}
62
63impl RawAttachment {
64    /// Creates a new [`RawAttachment`] with the specified handler and
65    /// attachment.
66    ///
67    /// The returned attachment will embed the specified attachment and use the
68    /// specified handler for all operations.
69    #[inline]
70    pub fn new<A, H>(attachment: A) -> Self
71    where
72        A: 'static,
73        H: AttachmentHandler<A>,
74    {
75        let ptr = Box::new(AttachmentData::new::<H>(attachment));
76        let ptr: *mut AttachmentData<A> = Box::into_raw(ptr);
77        let ptr: *mut AttachmentData<Erased> = ptr.cast::<AttachmentData<Erased>>();
78
79        // SAFETY: `Box::into_raw` returns a non-null pointer
80        let ptr: NonNull<AttachmentData<Erased>> = unsafe {
81            // @add-unsafe-context: Erased
82            NonNull::new_unchecked(ptr)
83        };
84
85        Self { ptr }
86    }
87
88    /// Returns a reference to the [`AttachmentData`] instance.
89    #[inline]
90    pub fn as_ref(&self) -> RawAttachmentRef<'_> {
91        RawAttachmentRef {
92            ptr: self.ptr,
93            _marker: core::marker::PhantomData,
94        }
95    }
96}
97
98impl core::ops::Drop for RawAttachment {
99    #[inline]
100    fn drop(&mut self) {
101        let vtable = self.as_ref().vtable();
102
103        // SAFETY:
104        // 1. The pointer comes from `Box::into_raw` (guaranteed by
105        //    `RawAttachment::new`)
106        // 2. The vtable returned by `self.as_ref().vtable()` is guaranteed to match the
107        //    data in the `AttachmentData`.
108        // 3. The pointer is initialized and has not been previously free as guaranteed
109        //    by the invariants on this type. We are correctly transferring ownership
110        //    here and the pointer is not used afterwards, as we are in the drop
111        //    function.
112        unsafe {
113            // @add-unsafe-context: AttachmentData
114            vtable.drop(self.ptr);
115        }
116    }
117}
118
119/// A lifetime-bound pointer to an [`AttachmentData`] that is guaranteed to
120/// point to an initialized instance of an [`AttachmentData<A>`] for some
121/// specific `A`, though we do not know which actual `A` it is.
122///
123/// We cannot use a [`&'a AttachmentData<A>`] directly, because that would
124/// require us to know the actual type of the attachment, which we do not.
125///
126/// [`&'a AttachmentData<A>`]: AttachmentData
127#[derive(Clone, Copy)]
128#[repr(transparent)]
129pub struct RawAttachmentRef<'a> {
130    /// Pointer to the inner attachment data
131    ///
132    /// # Safety
133    ///
134    /// The following safety invariants are guaranteed to be upheld as long as
135    /// this struct exists:
136    ///
137    /// 1. The pointer must have been created from a `Box<AttachmentData<A>>`
138    ///    for some `A` using `Box::into_raw`.
139    /// 2. The pointer will point to the same `AttachmentData<A>` for the entire
140    ///    lifetime of this object.
141    ptr: NonNull<AttachmentData<Erased>>,
142
143    /// Marker to tell the compiler that we should
144    /// behave the same as a `&'a AttachmentData<Erased>`
145    _marker: core::marker::PhantomData<&'a AttachmentData<Erased>>,
146}
147
148impl<'a> RawAttachmentRef<'a> {
149    /// Casts the [`RawAttachmentRef`] to an [`AttachmentData<A>`] reference.
150    ///
151    /// # Safety
152    ///
153    /// The caller must ensure:
154    ///
155    /// 1. The type `A` matches the actual attachment type stored in the
156    ///    [`AttachmentData`].
157    #[inline]
158    pub(super) unsafe fn cast_inner<A>(self) -> &'a AttachmentData<A> {
159        // Debug assertion to catch type mismatches in case of bugs
160        debug_assert_eq!(self.vtable().type_id(), TypeId::of::<A>());
161
162        let this = self.ptr.cast::<AttachmentData<A>>();
163        // SAFETY: Converting the NonNull pointer to a reference is sound because:
164        // - The pointer is non-null, properly aligned, and dereferenceable (guaranteed
165        //   by RawAttachmentRef's type invariants)
166        // - The pointee is properly initialized (RawAttachmentRef's doc comment
167        //   guarantees it points to an initialized AttachmentData<A> for some A)
168        // - The type `A` matches the actual attachment type (guaranteed by caller)
169        // - Shared access is allowed
170        // - The reference lifetime 'a is valid (tied to RawAttachmentRef<'a>'s
171        //   lifetime)
172        unsafe { this.as_ref() }
173    }
174
175    /// Returns a [`NonNull`] pointer to the [`AttachmentData`] instance.
176    #[inline]
177    pub(super) fn as_ptr(self) -> *const AttachmentData<Erased> {
178        self.ptr.as_ptr()
179    }
180
181    /// Returns the [`TypeId`] of the attachment.
182    #[inline]
183    pub fn attachment_type_id(self) -> TypeId {
184        self.vtable().type_id()
185    }
186
187    /// Returns the [`core::any::type_name`] of the attachment.
188    #[inline]
189    pub fn attachment_type_name(self) -> &'static str {
190        self.vtable().type_name()
191    }
192
193    /// Returns the [`TypeId`] of the attachment.
194    #[inline]
195    pub fn attachment_handler_type_id(self) -> TypeId {
196        self.vtable().handler_type_id()
197    }
198
199    /// Formats the attachment by using the [`AttachmentHandler::display`]
200    /// method specified by the handler used to create the
201    /// [`AttachmentData`].
202    #[inline]
203    pub fn attachment_display(self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204        let vtable = self.vtable();
205        // SAFETY:
206        // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
207        //    the `AttachmentData`.
208        unsafe {
209            // @add-unsafe-context: AttachmentData
210            vtable.display(self, formatter)
211        }
212    }
213
214    /// Formats the attachment by using the [`AttachmentHandler::debug`] method
215    /// specified by the handler used to create the [`AttachmentData`].
216    #[inline]
217    pub fn attachment_debug(self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
218        let vtable = self.vtable();
219
220        // SAFETY:
221        // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
222        //    the `AttachmentData`.
223        unsafe {
224            // @add-unsafe-context: AttachmentData
225            vtable.debug(self, formatter)
226        }
227    }
228
229    /// The formatting style preferred by the attachment when formatted as part
230    /// of a report.
231    ///
232    /// # Arguments
233    ///
234    /// - `report_formatting_function`: Whether the report in which this
235    ///   attachment will be embedded is being formatted using [`Display`]
236    ///   formatting or [`Debug`]
237    ///
238    /// [`Display`]: core::fmt::Display
239    /// [`Debug`]: core::fmt::Debug
240    #[inline]
241    pub fn preferred_formatting_style(
242        self,
243        report_formatting_function: FormattingFunction,
244    ) -> AttachmentFormattingStyle {
245        let vtable = self.vtable();
246
247        // SAFETY:
248        // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
249        //    the `AttachmentData`.
250        unsafe {
251            // @add-unsafe-context: AttachmentData
252            vtable.preferred_formatting_style(self, report_formatting_function)
253        }
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use alloc::string::String;
260
261    use super::*;
262    use crate::handlers::AttachmentHandler;
263
264    struct HandlerI32;
265    impl AttachmentHandler<i32> for HandlerI32 {
266        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
267            core::fmt::Display::fmt(value, formatter)
268        }
269
270        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
271            core::fmt::Debug::fmt(value, formatter)
272        }
273    }
274
275    struct HandlerString;
276    impl AttachmentHandler<String> for HandlerString {
277        fn display(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
278            core::fmt::Display::fmt(value, formatter)
279        }
280
281        fn debug(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
282            core::fmt::Debug::fmt(value, formatter)
283        }
284    }
285
286    #[test]
287    fn test_raw_attachment_size() {
288        assert_eq!(
289            core::mem::size_of::<RawAttachment>(),
290            core::mem::size_of::<usize>()
291        );
292        assert_eq!(
293            core::mem::size_of::<Option<RawAttachment>>(),
294            core::mem::size_of::<usize>()
295        );
296        assert_eq!(
297            core::mem::size_of::<Result<(), RawAttachment>>(),
298            core::mem::size_of::<usize>()
299        );
300        assert_eq!(
301            core::mem::size_of::<Result<String, RawAttachment>>(),
302            core::mem::size_of::<String>()
303        );
304        assert_eq!(
305            core::mem::size_of::<Option<Option<RawAttachment>>>(),
306            core::mem::size_of::<Option<usize>>()
307        );
308
309        assert_eq!(
310            core::mem::size_of::<RawAttachmentRef<'_>>(),
311            core::mem::size_of::<usize>()
312        );
313        assert_eq!(
314            core::mem::size_of::<Option<RawAttachmentRef<'_>>>(),
315            core::mem::size_of::<usize>()
316        );
317        assert_eq!(
318            core::mem::size_of::<Result<(), RawAttachmentRef<'_>>>(),
319            core::mem::size_of::<usize>()
320        );
321        assert_eq!(
322            core::mem::size_of::<Result<String, RawAttachmentRef<'_>>>(),
323            core::mem::size_of::<String>()
324        );
325        assert_eq!(
326            core::mem::size_of::<Option<Option<RawAttachmentRef<'_>>>>(),
327            core::mem::size_of::<Option<usize>>()
328        );
329    }
330
331    #[test]
332    fn test_raw_attachment_get_refs() {
333        let attachment = RawAttachment::new::<i32, HandlerI32>(100);
334        let attachment_ref = attachment.as_ref();
335
336        // Accessing the pointer multiple times should be safe and consistent
337        let ptr1 = attachment_ref.as_ptr();
338        let ptr2 = attachment_ref.as_ptr();
339        assert_eq!(ptr1, ptr2);
340    }
341
342    #[test]
343    fn test_raw_attachment_downcast() {
344        let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
345        let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
346
347        let int_ref = int_attachment.as_ref();
348        let string_ref = string_attachment.as_ref();
349
350        // Are TypeIds what we expect?
351        assert_eq!(int_ref.attachment_type_id(), TypeId::of::<i32>());
352        assert_eq!(string_ref.attachment_type_id(), TypeId::of::<String>());
353
354        // The vtables should be different
355        assert!(!core::ptr::eq(int_ref.vtable(), string_ref.vtable()));
356    }
357
358    #[test]
359    fn test_raw_attachment_display_debug() {
360        use alloc::format;
361
362        let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
363        let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
364
365        let int_ref = int_attachment.as_ref();
366        let string_ref = string_attachment.as_ref();
367
368        // Test display formatting
369        let display_int = format!(
370            "{}",
371            TestDisplayFormatter::new(|f| int_ref.attachment_display(f))
372        );
373        let display_string = format!(
374            "{}",
375            TestDisplayFormatter::new(|f| string_ref.attachment_display(f))
376        );
377
378        assert_eq!(display_int, "42");
379        assert_eq!(display_string, "test");
380
381        // Test debug formatting
382        let debug_int = format!(
383            "{}",
384            TestDisplayFormatter::new(|f| int_ref.attachment_debug(f))
385        );
386        let debug_string = format!(
387            "{}",
388            TestDisplayFormatter::new(|f| string_ref.attachment_debug(f))
389        );
390
391        assert_eq!(debug_int, "42");
392        assert_eq!(debug_string, "\"test\"");
393    }
394
395    // Helper struct for testing display/debug formatting
396    struct TestDisplayFormatter<F> {
397        formatter_fn: F,
398    }
399
400    impl<F> TestDisplayFormatter<F>
401    where
402        F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
403    {
404        fn new(formatter_fn: F) -> Self {
405            Self { formatter_fn }
406        }
407    }
408
409    impl<F> core::fmt::Display for TestDisplayFormatter<F>
410    where
411        F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
412    {
413        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
414            (self.formatter_fn)(f)
415        }
416    }
417
418    #[test]
419    fn test_send_sync() {
420        static_assertions::assert_not_impl_any!(RawAttachment: Send, Sync);
421        static_assertions::assert_not_impl_any!(RawAttachmentRef<'_>: Send, Sync);
422    }
423}