Skip to main content

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    /// 4. The pointer is the sole owner of the `AttachmentData` instance.
61    ptr: NonNull<AttachmentData<Erased>>,
62}
63
64impl RawAttachment {
65    /// Creates a new [`RawAttachment`] with the specified handler and
66    /// attachment.
67    ///
68    /// The returned attachment will embed the specified attachment and use the
69    /// specified handler for all operations.
70    #[inline]
71    pub fn new<A, H>(attachment: A) -> Self
72    where
73        A: 'static,
74        H: AttachmentHandler<A>,
75    {
76        let ptr = Box::new(AttachmentData::new::<H>(attachment));
77        let ptr: *mut AttachmentData<A> = Box::into_raw(ptr);
78        let ptr: *mut AttachmentData<Erased> = ptr.cast::<AttachmentData<Erased>>();
79
80        // SAFETY: `Box::into_raw` returns a non-null pointer
81        let ptr: NonNull<AttachmentData<Erased>> = unsafe {
82            // @add-unsafe-context: Erased
83            NonNull::new_unchecked(ptr)
84        };
85
86        Self {
87            // SAFETY:
88            // 1. See above
89            // 2. N/A
90            // 3. N/A
91            // 4. The Box is consumed, so we are the sole owner
92            ptr,
93        }
94    }
95
96    /// Returns a reference to the [`AttachmentData`] instance.
97    #[inline]
98    pub fn as_ref(&self) -> RawAttachmentRef<'_> {
99        RawAttachmentRef {
100            // SAFETY:
101            // 1. The pointer comes from `Box::into_raw` (guaranteed by `self`'s invariant)
102            // 2. We are creating the `RawAttachmentRef` here, and we are not changing the pointer
103            // 3. The returned `RawAttachmentRef<'b>` represents shared access for lifetime
104            //    `'b`, which is valid because we borrow `self` for `'b`, preventing any
105            //    mutation through `self` while the returned reference exists
106            ptr: self.ptr,
107            _marker: core::marker::PhantomData,
108        }
109    }
110
111    /// Returns a mutable reference to the [`AttachmentData`] instance.
112    #[inline]
113    pub fn as_mut(&mut self) -> RawAttachmentMut<'_> {
114        RawAttachmentMut {
115            // SAFETY:
116            // 1. Upheld by invariant on `self`
117            // 2. We are creating the `RawAttachmentMut` here, and we are
118            //    not changing the pointer
119            // 3. Upheld by mutable borrow of `self`
120            ptr: self.ptr,
121            _marker: core::marker::PhantomData,
122        }
123    }
124}
125
126impl core::ops::Drop for RawAttachment {
127    #[inline]
128    fn drop(&mut self) {
129        let vtable = self.as_ref().vtable();
130
131        // SAFETY:
132        // 1. The pointer comes from `Box::into_raw` (guaranteed by
133        //    `RawAttachment::new`)
134        // 2. The vtable returned by `self.as_ref().vtable()` is guaranteed to match the
135        //    data in the `AttachmentData`.
136        // 3. The pointer is initialized and has not been previously freed as guaranteed
137        //    by the invariants on this type. We are correctly transferring ownership
138        //    here and the pointer is not used afterwards, as we are in the drop
139        //    function.
140        unsafe {
141            // @add-unsafe-context: AttachmentData
142            vtable.drop(self.ptr);
143        }
144    }
145}
146
147/// A lifetime-bound pointer to an [`AttachmentData`] that is guaranteed to
148/// point to an initialized instance of an [`AttachmentData<A>`] for some
149/// specific `A`, though we do not know which actual `A` it is.
150///
151/// We cannot use a [`&'a AttachmentData<A>`] directly, because that would
152/// require us to know the actual type of the attachment, which we do not.
153///
154/// [`&'a AttachmentData<A>`]: AttachmentData
155#[derive(Clone, Copy)]
156#[repr(transparent)]
157pub struct RawAttachmentRef<'a> {
158    /// Pointer to the inner attachment data
159    ///
160    /// # Safety
161    ///
162    /// The following safety invariants are guaranteed to be upheld as long as
163    /// this struct exists:
164    ///
165    /// 1. The pointer must have been created from a `Box<AttachmentData<A>>`
166    ///    for some `A` using `Box::into_raw`.
167    /// 2. The pointer will point to the same `AttachmentData<A>` for the entire
168    ///    lifetime of this object.
169    /// 3. This pointer represents read-only access to the `AttachmentData` for
170    ///    the lifetime `'a` with the same semantics as a `&'a
171    ///    AttachmentData<C>`.
172    ptr: NonNull<AttachmentData<Erased>>,
173
174    /// Marker to tell the compiler that we should
175    /// behave the same as a `&'a AttachmentData<Erased>`
176    _marker: core::marker::PhantomData<&'a AttachmentData<Erased>>,
177}
178
179impl<'a> RawAttachmentRef<'a> {
180    /// Casts the [`RawAttachmentRef`] to an [`AttachmentData<A>`] reference.
181    ///
182    /// # Safety
183    ///
184    /// The caller must ensure:
185    ///
186    /// 1. The type `A` matches the actual attachment type stored in the
187    ///    [`AttachmentData`].
188    #[inline]
189    pub(super) unsafe fn cast_inner<A>(self) -> &'a AttachmentData<A> {
190        // Debug assertion to catch type mismatches in case of bugs
191        debug_assert_eq!(self.vtable().type_id(), TypeId::of::<A>());
192
193        let this = self.ptr.cast::<AttachmentData<A>>();
194        // SAFETY: Converting the NonNull pointer to a reference is sound because:
195        // - The pointer is non-null, properly aligned, and dereferenceable (guaranteed
196        //   by RawAttachmentRef's type invariants)
197        // - The pointee is properly initialized (RawAttachmentRef's doc comment
198        //   guarantees it points to an initialized AttachmentData<A> for some A)
199        // - The type `A` matches the actual attachment type (guaranteed by caller)
200        // - Shared access is allowed
201        // - The reference lifetime 'a is valid (tied to RawAttachmentRef<'a>'s
202        //   lifetime)
203        unsafe { this.as_ref() }
204    }
205
206    /// Returns a [`NonNull`] pointer to the [`AttachmentData`] instance.
207    #[inline]
208    pub(super) fn as_ptr(self) -> *const AttachmentData<Erased> {
209        self.ptr.as_ptr()
210    }
211
212    /// Returns the [`TypeId`] of the attachment.
213    #[inline]
214    pub fn attachment_type_id(self) -> TypeId {
215        self.vtable().type_id()
216    }
217
218    /// Returns the [`core::any::type_name`] of the attachment.
219    #[inline]
220    pub fn attachment_type_name(self) -> &'static str {
221        self.vtable().type_name()
222    }
223
224    /// Returns the [`TypeId`] of the attachment handler.
225    #[inline]
226    pub fn attachment_handler_type_id(self) -> TypeId {
227        self.vtable().handler_type_id()
228    }
229
230    /// Formats the attachment by using the [`AttachmentHandler::display`]
231    /// method specified by the handler used to create the
232    /// [`AttachmentData`].
233    #[inline]
234    pub fn attachment_display(self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
235        let vtable = self.vtable();
236        // SAFETY:
237        // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
238        //    the `AttachmentData`.
239        unsafe {
240            // @add-unsafe-context: AttachmentData
241            vtable.display(self, formatter)
242        }
243    }
244
245    /// Formats the attachment by using the [`AttachmentHandler::debug`] method
246    /// specified by the handler used to create the [`AttachmentData`].
247    #[inline]
248    pub fn attachment_debug(self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249        let vtable = self.vtable();
250
251        // SAFETY:
252        // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
253        //    the `AttachmentData`.
254        unsafe {
255            // @add-unsafe-context: AttachmentData
256            vtable.debug(self, formatter)
257        }
258    }
259
260    /// The formatting style preferred by the attachment when formatted as part
261    /// of a report.
262    ///
263    /// # Arguments
264    ///
265    /// - `report_formatting_function`: Whether the report in which this
266    ///   attachment will be embedded is being formatted using [`Display`]
267    ///   formatting or [`Debug`]
268    ///
269    /// [`Display`]: core::fmt::Display
270    /// [`Debug`]: core::fmt::Debug
271    #[inline]
272    pub fn preferred_formatting_style(
273        self,
274        report_formatting_function: FormattingFunction,
275    ) -> AttachmentFormattingStyle {
276        let vtable = self.vtable();
277
278        // SAFETY:
279        // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
280        //    the `AttachmentData`.
281        unsafe {
282            // @add-unsafe-context: AttachmentData
283            vtable.preferred_formatting_style(self, report_formatting_function)
284        }
285    }
286}
287
288/// A mutable lifetime-bound pointer to an [`AttachmentData`] that is guaranteed
289/// to be the sole mutable pointer to an initialized instance of an
290/// [`AttachmentData<A>`] for some specific `A`, though we do not know which
291/// actual `A` it is.
292///
293/// We cannot use a [`&'a mut AttachmentData<A>`] directly, because that would
294/// require us to know the actual type of the attachment, which we do not.
295///
296/// [`&'a mut AttachmentData<A>`]: AttachmentData
297#[repr(transparent)]
298pub struct RawAttachmentMut<'a> {
299    /// Pointer to the inner attachment data
300    ///
301    /// # Safety
302    ///
303    /// The following safety invariants are guaranteed to be upheld as long as
304    /// this struct exists:
305    ///
306    /// 1. The pointer must have been created from a `Box<AttachmentData<A>>`
307    ///    for some `A` using `Box::into_raw`.
308    /// 2. The pointer will point to the same `AttachmentData<A>` for the entire
309    ///    lifetime of this object.
310    /// 3. This pointer represents exclusive mutable access to the
311    ///    `AttachmentData` for the lifetime `'a` with the same semantics as a
312    ///    `&'a mut AttachmentData<C>`.
313    ptr: NonNull<AttachmentData<Erased>>,
314
315    /// Marker to tell the compiler that we should
316    /// behave the same as a `&'a mut AttachmentData<Erased>`
317    _marker: core::marker::PhantomData<&'a mut AttachmentData<Erased>>,
318}
319
320impl<'a> RawAttachmentMut<'a> {
321    /// Casts the [`RawAttachmentMut`] to an [`AttachmentData<A>`] mutable
322    /// reference.
323    ///
324    /// # Safety
325    ///
326    /// The caller must ensure:
327    ///
328    /// 1. The type `A` matches the actual attachment type stored in the
329    ///    [`AttachmentData`].
330    #[inline]
331    pub(super) unsafe fn cast_inner<A>(self) -> &'a mut AttachmentData<A> {
332        // Debug assertion to catch type mismatches in case of bugs
333        debug_assert_eq!(self.as_ref().vtable().type_id(), TypeId::of::<A>());
334
335        let mut this = self.ptr.cast::<AttachmentData<A>>();
336        // SAFETY: Converting the NonNull pointer to a mutable reference is sound
337        // because:
338        // - The pointer is non-null, properly aligned, and dereferenceable (guaranteed
339        //   by RawAttachmentMut's type invariants)
340        // - The pointee is properly initialized (RawAttachmentMut's doc comment
341        //   guarantees it is the exclusive pointer to an initialized AttachmentData<A>
342        //   for some A)
343        // - The type `A` matches the actual attachment type (guaranteed by caller)
344        // - Shared access is NOT allowed
345        // - The reference lifetime 'a is valid (tied to RawAttachmentMut<'a>'s
346        //   lifetime)
347        unsafe { this.as_mut() }
348    }
349
350    /// Reborrows the mutable reference to the [`AttachmentData`] with a shorter
351    /// lifetime.
352    #[inline]
353    pub fn reborrow<'b>(&'b mut self) -> RawAttachmentMut<'b> {
354        RawAttachmentMut {
355            // SAFETY:
356            // 1. The pointer comes from `Box::into_raw` (guaranteed by `self`'s invariant)
357            // 2. We are creating the `RawAttachmentMut` here, and we are not changing the pointer
358            // 3. Exclusive mutable access for lifetime `'b` is guaranteed because:
359            //    - The returned `RawAttachmentMut<'b>` contains `PhantomData<&'b mut ...>`
360            //    - This causes the borrow checker to treat the return value as borrowing `self` for `'b`
361            //    - Therefore `self` cannot be used while the returned value exists
362            ptr: self.ptr,
363            _marker: core::marker::PhantomData,
364        }
365    }
366
367    /// Returns a reference to the [`AttachmentData`] instance.
368    #[inline]
369    pub fn as_ref<'b: 'a>(&'b self) -> RawAttachmentRef<'b> {
370        RawAttachmentRef {
371            // SAFETY:
372            // 1. The pointer comes from `Box::into_raw` (guaranteed by `self`'s invariant)
373            // 2. We are creating the `RawAttachmentRef` here, and we are not changing the pointer
374            // 3. The returned `RawAttachmentRef<'b>` represents shared access for lifetime
375            //    `'b`, which is valid because we borrow `self` for `'b`, preventing any
376            //    mutation through `self` while the returned reference exists
377            ptr: self.ptr,
378            _marker: core::marker::PhantomData,
379        }
380    }
381
382    /// Consumes the mutable reference and returns an immutable one with the
383    /// same lifetime.
384    #[inline]
385    pub fn into_ref(self) -> RawAttachmentRef<'a> {
386        RawAttachmentRef {
387            // SAFETY:
388            // 1. The pointer comes from `Box::into_raw` (guaranteed by `self`'s invariant)
389            // 2. We are creating the `RawAttachmentRef` here, and we are not changing the pointer
390            // 3. The returned `RawAttachmentRef<'a>` represents shared access for lifetime
391            //    `'a`, which is valid because we are consuming `self` and turning it into a shared reference
392            ptr: self.ptr,
393            _marker: core::marker::PhantomData,
394        }
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use alloc::string::String;
401
402    use super::*;
403    use crate::handlers::AttachmentHandler;
404
405    struct HandlerI32;
406    impl AttachmentHandler<i32> for HandlerI32 {
407        fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
408            core::fmt::Display::fmt(value, formatter)
409        }
410
411        fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
412            core::fmt::Debug::fmt(value, formatter)
413        }
414    }
415
416    struct HandlerString;
417    impl AttachmentHandler<String> for HandlerString {
418        fn display(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
419            core::fmt::Display::fmt(value, formatter)
420        }
421
422        fn debug(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
423            core::fmt::Debug::fmt(value, formatter)
424        }
425    }
426
427    #[test]
428    fn test_raw_attachment_size() {
429        assert_eq!(
430            core::mem::size_of::<RawAttachment>(),
431            core::mem::size_of::<usize>()
432        );
433        assert_eq!(
434            core::mem::size_of::<Option<RawAttachment>>(),
435            core::mem::size_of::<usize>()
436        );
437        assert_eq!(
438            core::mem::size_of::<Result<(), RawAttachment>>(),
439            core::mem::size_of::<usize>()
440        );
441        assert_eq!(
442            core::mem::size_of::<Result<String, RawAttachment>>(),
443            core::mem::size_of::<String>()
444        );
445        assert_eq!(
446            core::mem::size_of::<Option<Option<RawAttachment>>>(),
447            core::mem::size_of::<Option<usize>>()
448        );
449
450        assert_eq!(
451            core::mem::size_of::<RawAttachmentRef<'_>>(),
452            core::mem::size_of::<usize>()
453        );
454        assert_eq!(
455            core::mem::size_of::<Option<RawAttachmentRef<'_>>>(),
456            core::mem::size_of::<usize>()
457        );
458        assert_eq!(
459            core::mem::size_of::<Result<(), RawAttachmentRef<'_>>>(),
460            core::mem::size_of::<usize>()
461        );
462        assert_eq!(
463            core::mem::size_of::<Result<String, RawAttachmentRef<'_>>>(),
464            core::mem::size_of::<String>()
465        );
466        assert_eq!(
467            core::mem::size_of::<Option<Option<RawAttachmentRef<'_>>>>(),
468            core::mem::size_of::<Option<usize>>()
469        );
470
471        assert_eq!(
472            core::mem::size_of::<RawAttachmentMut<'_>>(),
473            core::mem::size_of::<usize>()
474        );
475        assert_eq!(
476            core::mem::size_of::<Option<RawAttachmentMut<'_>>>(),
477            core::mem::size_of::<usize>()
478        );
479        assert_eq!(
480            core::mem::size_of::<Result<(), RawAttachmentMut<'_>>>(),
481            core::mem::size_of::<usize>()
482        );
483        assert_eq!(
484            core::mem::size_of::<Result<String, RawAttachmentMut<'_>>>(),
485            core::mem::size_of::<String>()
486        );
487        assert_eq!(
488            core::mem::size_of::<Option<Option<RawAttachmentMut<'_>>>>(),
489            core::mem::size_of::<Option<usize>>()
490        );
491    }
492
493    #[test]
494    fn test_raw_attachment_get_refs() {
495        let attachment = RawAttachment::new::<i32, HandlerI32>(100);
496        let attachment_ref = attachment.as_ref();
497
498        // Accessing the pointer multiple times should be safe and consistent
499        let ptr1 = attachment_ref.as_ptr();
500        let ptr2 = attachment_ref.as_ptr();
501        assert_eq!(ptr1, ptr2);
502    }
503
504    #[test]
505    fn test_raw_attachment_downcast() {
506        let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
507        let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
508
509        let int_ref = int_attachment.as_ref();
510        let string_ref = string_attachment.as_ref();
511
512        // Are TypeIds what we expect?
513        assert_eq!(int_ref.attachment_type_id(), TypeId::of::<i32>());
514        assert_eq!(string_ref.attachment_type_id(), TypeId::of::<String>());
515
516        // The vtables should be different
517        assert!(!core::ptr::eq(int_ref.vtable(), string_ref.vtable()));
518    }
519
520    #[test]
521    fn test_raw_attachment_display_debug() {
522        use alloc::format;
523
524        let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
525        let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
526
527        let int_ref = int_attachment.as_ref();
528        let string_ref = string_attachment.as_ref();
529
530        // Test display formatting
531        let display_int = format!(
532            "{}",
533            TestDisplayFormatter::new(|f| int_ref.attachment_display(f))
534        );
535        let display_string = format!(
536            "{}",
537            TestDisplayFormatter::new(|f| string_ref.attachment_display(f))
538        );
539
540        assert_eq!(display_int, "42");
541        assert_eq!(display_string, "test");
542
543        // Test debug formatting
544        let debug_int = format!(
545            "{}",
546            TestDisplayFormatter::new(|f| int_ref.attachment_debug(f))
547        );
548        let debug_string = format!(
549            "{}",
550            TestDisplayFormatter::new(|f| string_ref.attachment_debug(f))
551        );
552
553        assert_eq!(debug_int, "42");
554        assert_eq!(debug_string, "\"test\"");
555    }
556
557    // Helper struct for testing display/debug formatting
558    struct TestDisplayFormatter<F> {
559        formatter_fn: F,
560    }
561
562    impl<F> TestDisplayFormatter<F>
563    where
564        F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
565    {
566        fn new(formatter_fn: F) -> Self {
567            Self { formatter_fn }
568        }
569    }
570
571    impl<F> core::fmt::Display for TestDisplayFormatter<F>
572    where
573        F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
574    {
575        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
576            (self.formatter_fn)(f)
577        }
578    }
579
580    #[test]
581    fn test_send_sync() {
582        static_assertions::assert_not_impl_any!(RawAttachment: Send, Sync);
583        static_assertions::assert_not_impl_any!(RawAttachmentRef<'_>: Send, Sync);
584        static_assertions::assert_not_impl_any!(RawAttachmentMut<'_>: Send, Sync);
585    }
586}