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