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}