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 [`TypeId`] of the attachment.
188 #[inline]
189 pub fn attachment_handler_type_id(self) -> TypeId {
190 self.vtable().handler_type_id()
191 }
192
193 /// Formats the attachment by using the [`AttachmentHandler::display`]
194 /// method specified by the handler used to create the
195 /// [`AttachmentData`].
196 #[inline]
197 pub fn attachment_display(self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
198 let vtable = self.vtable();
199 // SAFETY:
200 // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
201 // the `AttachmentData`.
202 unsafe {
203 // @add-unsafe-context: AttachmentData
204 vtable.display(self, formatter)
205 }
206 }
207
208 /// Formats the attachment by using the [`AttachmentHandler::debug`] method
209 /// specified by the handler used to create the [`AttachmentData`].
210 #[inline]
211 pub fn attachment_debug(self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
212 let vtable = self.vtable();
213
214 // SAFETY:
215 // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
216 // the `AttachmentData`.
217 unsafe {
218 // @add-unsafe-context: AttachmentData
219 vtable.debug(self, formatter)
220 }
221 }
222
223 /// The formatting style preferred by the attachment when formatted as part
224 /// of a report.
225 ///
226 /// # Arguments
227 ///
228 /// - `report_formatting_function`: Whether the report in which this
229 /// attachment will be embedded is being formatted using [`Display`]
230 /// formatting or [`Debug`]
231 ///
232 /// [`Display`]: core::fmt::Display
233 /// [`Debug`]: core::fmt::Debug
234 #[inline]
235 pub fn preferred_formatting_style(
236 self,
237 report_formatting_function: FormattingFunction,
238 ) -> AttachmentFormattingStyle {
239 let vtable = self.vtable();
240
241 // SAFETY:
242 // 1. The vtable returned by `self.vtable()` is guaranteed to match the data in
243 // the `AttachmentData`.
244 unsafe {
245 // @add-unsafe-context: AttachmentData
246 vtable.preferred_formatting_style(self, report_formatting_function)
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use alloc::string::String;
254
255 use super::*;
256 use crate::handlers::AttachmentHandler;
257
258 struct HandlerI32;
259 impl AttachmentHandler<i32> for HandlerI32 {
260 fn display(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
261 core::fmt::Display::fmt(value, formatter)
262 }
263
264 fn debug(value: &i32, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
265 core::fmt::Debug::fmt(value, formatter)
266 }
267 }
268
269 struct HandlerString;
270 impl AttachmentHandler<String> for HandlerString {
271 fn display(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
272 core::fmt::Display::fmt(value, formatter)
273 }
274
275 fn debug(value: &String, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
276 core::fmt::Debug::fmt(value, formatter)
277 }
278 }
279
280 #[test]
281 fn test_raw_attachment_size() {
282 assert_eq!(
283 core::mem::size_of::<RawAttachment>(),
284 core::mem::size_of::<usize>()
285 );
286 assert_eq!(
287 core::mem::size_of::<Option<RawAttachment>>(),
288 core::mem::size_of::<usize>()
289 );
290 assert_eq!(
291 core::mem::size_of::<Result<(), RawAttachment>>(),
292 core::mem::size_of::<usize>()
293 );
294 assert_eq!(
295 core::mem::size_of::<Result<String, RawAttachment>>(),
296 core::mem::size_of::<String>()
297 );
298 assert_eq!(
299 core::mem::size_of::<Option<Option<RawAttachment>>>(),
300 core::mem::size_of::<Option<usize>>()
301 );
302
303 assert_eq!(
304 core::mem::size_of::<RawAttachmentRef<'_>>(),
305 core::mem::size_of::<usize>()
306 );
307 assert_eq!(
308 core::mem::size_of::<Option<RawAttachmentRef<'_>>>(),
309 core::mem::size_of::<usize>()
310 );
311 assert_eq!(
312 core::mem::size_of::<Result<(), RawAttachmentRef<'_>>>(),
313 core::mem::size_of::<usize>()
314 );
315 assert_eq!(
316 core::mem::size_of::<Result<String, RawAttachmentRef<'_>>>(),
317 core::mem::size_of::<String>()
318 );
319 assert_eq!(
320 core::mem::size_of::<Option<Option<RawAttachmentRef<'_>>>>(),
321 core::mem::size_of::<Option<usize>>()
322 );
323 }
324
325 #[test]
326 fn test_raw_attachment_get_refs() {
327 let attachment = RawAttachment::new::<i32, HandlerI32>(100);
328 let attachment_ref = attachment.as_ref();
329
330 // Accessing the pointer multiple times should be safe and consistent
331 let ptr1 = attachment_ref.as_ptr();
332 let ptr2 = attachment_ref.as_ptr();
333 assert_eq!(ptr1, ptr2);
334 }
335
336 #[test]
337 fn test_raw_attachment_downcast() {
338 let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
339 let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
340
341 let int_ref = int_attachment.as_ref();
342 let string_ref = string_attachment.as_ref();
343
344 // Are TypeIds what we expect?
345 assert_eq!(int_ref.attachment_type_id(), TypeId::of::<i32>());
346 assert_eq!(string_ref.attachment_type_id(), TypeId::of::<String>());
347
348 // The vtables should be different
349 assert!(!core::ptr::eq(int_ref.vtable(), string_ref.vtable()));
350 }
351
352 #[test]
353 fn test_raw_attachment_display_debug() {
354 use alloc::format;
355
356 let int_attachment = RawAttachment::new::<i32, HandlerI32>(42);
357 let string_attachment = RawAttachment::new::<String, HandlerString>(String::from("test"));
358
359 let int_ref = int_attachment.as_ref();
360 let string_ref = string_attachment.as_ref();
361
362 // Test display formatting
363 let display_int = format!(
364 "{}",
365 TestDisplayFormatter::new(|f| int_ref.attachment_display(f))
366 );
367 let display_string = format!(
368 "{}",
369 TestDisplayFormatter::new(|f| string_ref.attachment_display(f))
370 );
371
372 assert_eq!(display_int, "42");
373 assert_eq!(display_string, "test");
374
375 // Test debug formatting
376 let debug_int = format!(
377 "{}",
378 TestDisplayFormatter::new(|f| int_ref.attachment_debug(f))
379 );
380 let debug_string = format!(
381 "{}",
382 TestDisplayFormatter::new(|f| string_ref.attachment_debug(f))
383 );
384
385 assert_eq!(debug_int, "42");
386 assert_eq!(debug_string, "\"test\"");
387 }
388
389 // Helper struct for testing display/debug formatting
390 struct TestDisplayFormatter<F> {
391 formatter_fn: F,
392 }
393
394 impl<F> TestDisplayFormatter<F>
395 where
396 F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
397 {
398 fn new(formatter_fn: F) -> Self {
399 Self { formatter_fn }
400 }
401 }
402
403 impl<F> core::fmt::Display for TestDisplayFormatter<F>
404 where
405 F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
406 {
407 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
408 (self.formatter_fn)(f)
409 }
410 }
411
412 #[test]
413 fn test_send_sync() {
414 static_assertions::assert_not_impl_any!(RawAttachment: Send, Sync);
415 static_assertions::assert_not_impl_any!(RawAttachmentRef<'_>: Send, Sync);
416 }
417}