From 355bf83b1565b6ddc7a4248811d4d0ae53c1d804 Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Mon, 5 Jan 2026 22:55:08 +0100 Subject: [PATCH 1/5] . --- rootcause-internals/src/attachment/data.rs | 29 ++++- rootcause-internals/src/attachment/mod.rs | 2 +- rootcause-internals/src/attachment/raw.rs | 119 ++++++++++++++++++++- rootcause-internals/src/lib.rs | 2 +- src/report_attachment/mod.rs | 1 + src/report_attachment/mut_.rs | 102 ++++++++++++++++++ src/report_attachment/ref_.rs | 1 + 7 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 src/report_attachment/mut_.rs diff --git a/rootcause-internals/src/attachment/data.rs b/rootcause-internals/src/attachment/data.rs index 340e913..ce99a30 100644 --- a/rootcause-internals/src/attachment/data.rs +++ b/rootcause-internals/src/attachment/data.rs @@ -19,8 +19,13 @@ //! pointer to `AttachmentData` without constructing an invalid //! reference to the full struct. +use core::any::TypeId; + use crate::{ - attachment::{raw::RawAttachmentRef, vtable::AttachmentVtable}, + attachment::{ + raw::{RawAttachmentMut, RawAttachmentRef}, + vtable::AttachmentVtable, + }, handlers::AttachmentHandler, }; @@ -112,6 +117,28 @@ impl<'a> RawAttachmentRef<'a> { } } +impl<'a> RawAttachmentMut<'a> { + /// Accesses the inner attachment of the [`AttachmentData`] instance as a + /// reference to the specified type. + /// + /// # Safety + /// + /// The caller must ensure: + /// + /// 1. The type `A` matches the actual attachment type stored in the + /// [`AttachmentData`]. + #[inline] + pub unsafe fn into_attachment_downcast_unchecked(self) -> &'a mut A { + // SAFETY: + // 1. Guaranteed by the caller + let this = unsafe { + // @add-unsafe-context: AttachmentData + self.cast_inner::() + }; + &mut this.attachment + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rootcause-internals/src/attachment/mod.rs b/rootcause-internals/src/attachment/mod.rs index d6e2a7f..a044ef3 100644 --- a/rootcause-internals/src/attachment/mod.rs +++ b/rootcause-internals/src/attachment/mod.rs @@ -20,4 +20,4 @@ pub(crate) mod data; pub(crate) mod raw; pub(crate) mod vtable; -pub use self::raw::{RawAttachment, RawAttachmentRef}; +pub use self::raw::{RawAttachment, RawAttachmentMut, RawAttachmentRef}; diff --git a/rootcause-internals/src/attachment/raw.rs b/rootcause-internals/src/attachment/raw.rs index 75d14a1..b215db1 100644 --- a/rootcause-internals/src/attachment/raw.rs +++ b/rootcause-internals/src/attachment/raw.rs @@ -23,7 +23,7 @@ //! attachments. use alloc::boxed::Box; -use core::{any::TypeId, ptr::NonNull}; +use core::{any::TypeId, marker::PhantomData, ptr::NonNull}; use crate::{ attachment::data::AttachmentData, @@ -190,7 +190,7 @@ impl<'a> RawAttachmentRef<'a> { self.vtable().type_name() } - /// Returns the [`TypeId`] of the attachment. + /// Returns the [`TypeId`] of the attachment handler. #[inline] pub fn attachment_handler_type_id(self) -> TypeId { self.vtable().handler_type_id() @@ -254,6 +254,99 @@ impl<'a> RawAttachmentRef<'a> { } } +/// A lifetime-bound non-shared(?) pointer to an [`AttachmentData`] that is guaranteed to +/// be the sole mutable(?) pointer to an initialized instance of an [`AttachmentData`] for some +/// specific `A`, though we do not know which actual `A` it is. +/// +/// We cannot use a [`&'a mut AttachmentData`] directly, because that would +/// require us to know the actual type of the attachment, which we do not. +/// +/// [`&'a mut AttachmentData`]: AttachmentData +#[repr(transparent)] +pub struct RawAttachmentMut<'a> { + /// Pointer to the inner attachment data + /// + /// # Safety + /// + /// The following safety invariants are guaranteed to be upheld as long as + /// this struct exists: + /// + /// 1. The pointer must have been created from a `Box>` + /// for some `A` using `Box::into_raw`. + /// 2. The pointer will point to the same `AttachmentData` for the entire + /// lifetime of this object. + /// 3. The pointer provides mutable access to the `AttachmentData` it + /// points to, no other pointers to this data exists except the owner(?). + ptr: NonNull>, + + /// Marker to tell the compiler that we should + /// behave the same as a `&'a mut AttachmentData` + _marker: core::marker::PhantomData<&'a mut AttachmentData>, +} + +impl<'a> RawAttachmentMut<'a> { + /// Casts the [`RawAttachmentMut`] to an [`AttachmentData`] mutable reference. + /// + /// # Safety + /// + /// The caller must ensure: + /// + /// 1. The type `A` matches the actual attachment type stored in the + /// [`AttachmentData`]. + #[inline] + pub(super) unsafe fn cast_inner(self) -> &'a mut AttachmentData { + // Debug assertion to catch type mismatches in case of bugs + debug_assert_eq!(self.as_ref().vtable().type_id(), TypeId::of::()); + + let mut this = self.ptr.cast::>(); + // SAFETY: Converting the NonNull pointer to a mutable reference is sound because: + // - The pointer is non-null, properly aligned, and dereferenceable (guaranteed + // by RawAttachmentMut's type invariants) + // - The pointee is properly initialized (RawAttachmentMut's doc comment + // guarantees it is the exclusive pointer to an initialized AttachmentData for some A) + // - The type `A` matches the actual attachment type (guaranteed by caller) + // - Shared access is NOT allowed + // - The reference lifetime 'a is valid (tied to RawAttachmentMut<'a>'s + // lifetime) + unsafe { this.as_mut() } + } + + /// Reborrows the mutable reference to the [`ReportData`] with a shorter + /// lifetime. + #[inline] + pub fn reborrow<'b>(&'b mut self) -> RawAttachmentMut<'b> { + RawAttachmentMut { + ptr: self.ptr, + _marker: core::marker::PhantomData, + } + } + + /// Consumes the mutable reference and returns an immutable one with the + /// same lifetime. + #[inline] + pub(super) fn into_ref(self) -> RawAttachmentRef<'a> { + RawAttachmentRef { + ptr: self.ptr, + _marker: PhantomData, + } + } + + /// Returns a reference to the [`AttachmentData`] instance. + #[inline] + pub fn as_ref<'b: 'a>(&'b self) -> RawAttachmentRef<'b> { + RawAttachmentRef { + ptr: self.ptr, + _marker: PhantomData, + } + } + + /// Returns a [`NonNull`] pointer to the [`AttachmentData`] instance. + #[inline] + pub(super) fn into_mut_ptr(self) -> *mut AttachmentData { + self.ptr.as_ptr() + } +} + #[cfg(test)] mod tests { use alloc::string::String; @@ -326,6 +419,27 @@ mod tests { core::mem::size_of::>>>(), core::mem::size_of::>() ); + + assert_eq!( + core::mem::size_of::>(), + core::mem::size_of::() + ); + assert_eq!( + core::mem::size_of::>>(), + core::mem::size_of::() + ); + assert_eq!( + core::mem::size_of::>>(), + core::mem::size_of::() + ); + assert_eq!( + core::mem::size_of::>>(), + core::mem::size_of::() + ); + assert_eq!( + core::mem::size_of::>>>(), + core::mem::size_of::>() + ); } #[test] @@ -419,5 +533,6 @@ mod tests { fn test_send_sync() { static_assertions::assert_not_impl_any!(RawAttachment: Send, Sync); static_assertions::assert_not_impl_any!(RawAttachmentRef<'_>: Send, Sync); + static_assertions::assert_not_impl_any!(RawAttachmentMut<'_>: Send, Sync); } } diff --git a/rootcause-internals/src/lib.rs b/rootcause-internals/src/lib.rs index 90df56d..e188890 100644 --- a/rootcause-internals/src/lib.rs +++ b/rootcause-internals/src/lib.rs @@ -86,5 +86,5 @@ pub mod handlers; mod report; mod util; -pub use attachment::{RawAttachment, RawAttachmentRef}; +pub use attachment::{RawAttachment, RawAttachmentMut, RawAttachmentRef}; pub use report::{RawReport, RawReportMut, RawReportRef}; diff --git a/src/report_attachment/mod.rs b/src/report_attachment/mod.rs index 35fa416..c089a5c 100644 --- a/src/report_attachment/mod.rs +++ b/src/report_attachment/mod.rs @@ -153,6 +153,7 @@ //! [`Display`]: crate::handlers::Display //! [`Debug`]: crate::handlers::Debug +mod mut_; mod owned; mod ref_; diff --git a/src/report_attachment/mut_.rs b/src/report_attachment/mut_.rs new file mode 100644 index 0000000..ef778ac --- /dev/null +++ b/src/report_attachment/mut_.rs @@ -0,0 +1,102 @@ +use alloc::fmt; +use core::any::TypeId; + +use rootcause_internals::handlers::{AttachmentFormattingStyle, FormattingFunction}; + +use crate::{markers::Dynamic, util::format_helper}; + +/// FIXME: Once rust-lang/rust#132922 gets resolved, we can make the `raw` field +/// an unsafe field and remove this module. +mod limit_field_access { + use core::marker::PhantomData; + + use rootcause_internals::RawAttachmentMut; + + use crate::markers::Dynamic; + + #[repr(transparent)] + pub struct ReportAttachmentMut<'a, Attachment: ?Sized + 'static = Dynamic> { + /// # Safety + /// + /// The following safety invariants are guaranteed to be upheld as long + /// as this struct exists: + /// + /// 1. `A` must either be a type bounded by `Sized`, or `Dynamic`. + /// 2. If `A` is a `Sized` type: The attachment embedded in the + /// [`RawAttachmentRef`] must be of type `A`. + raw: RawAttachmentMut<'a>, + _attachment: PhantomData, + } + + impl<'a, A: ?Sized> ReportAttachmentMut<'a, A> { + /// Creates a new AttachmentRef from a raw attachment reference + /// + /// # Safety + /// + /// The caller must ensure: + /// + /// 1. `A` must either be a type bounded by `Sized`, or `Dynamic`. + /// 2. If `A` is a `Sized` type: The attachment embedded in the + /// [`RawAttachmentMut`] must be of type `A`. + #[must_use] + pub(crate) unsafe fn from_raw(raw: RawAttachmentMut<'a>) -> Self { + // SAFETY: We must uphold the safety invariants of the raw field: + // 1. Guaranteed by the caller + // 2. Guaranteed by the caller + ReportAttachmentMut { + raw, + _attachment: PhantomData, + } + } + + /// Returns the underlying raw attachment reference + #[must_use] + pub(crate) fn into_raw_ref(self) -> RawAttachmentMut<'a> { + // We are destroying `self`, so we no longer + // need to uphold any safety invariants. + self.raw + } + } +} + +pub use limit_field_access::ReportAttachmentMut; + +impl<'a, A: Sized> ReportAttachmentMut<'a, A> { + /// Obtain the reference to the inner attachment data. + /// + /// This method provides direct access to the attachment's data when the + /// concrete type `A` is known at compile time. The attachment type must + /// be [`Sized`] to use this method. + /// + /// # Panics + /// This method will panic if the actual type of the attachment doesn't + /// match the type `A`. For a safe alternative that returns [`Option`], + /// use [`downcast_inner`] instead. + /// + /// # Examples + /// ``` + /// use rootcause::{ + /// prelude::*, + /// report_attachment::{ReportAttachment, ReportAttachmentMut}, + /// }; + /// + /// let mut attachment: ReportAttachment = ReportAttachment::new(40i32); + /// { + /// let attachment_mut: ReportAttachmentMut<'_, i32> = attachment.as_mut(); + /// let number: &mut i32 = attachment_ref.into_inner(); + /// *number += 2; + /// } + /// + /// assert_eq!(attachment.as_ref().inner(), &42i32); + /// ``` + /// + /// [`downcast_inner`]: Self::downcast_inner + #[must_use] + pub fn into_inner(self) -> &'a mut A { + let raw = self.into_raw_ref(); + + // SAFETY: + // 1. Guaranteed by the invariants of this type. + unsafe { raw.into_attachment_downcast_unchecked() } + } +} diff --git a/src/report_attachment/ref_.rs b/src/report_attachment/ref_.rs index 250d15b..545f7aa 100644 --- a/src/report_attachment/ref_.rs +++ b/src/report_attachment/ref_.rs @@ -85,6 +85,7 @@ mod limit_field_access { // 2. This remains true for both the original and the copy impl<'a, A: ?Sized> Copy for ReportAttachmentRef<'a, A> {} } + pub use limit_field_access::ReportAttachmentRef; impl<'a, A: ?Sized> Clone for ReportAttachmentRef<'a, A> { From a8c8f7fd7efa4ad1e735e4e052381c81e5ed9f53 Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Sat, 10 Jan 2026 01:12:02 +0100 Subject: [PATCH 2/5] . --- rootcause-internals/src/attachment/raw.rs | 12 ++--- src/report_attachment/mut_.rs | 56 +++++++++++++++++++++-- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/rootcause-internals/src/attachment/raw.rs b/rootcause-internals/src/attachment/raw.rs index b215db1..e1c8d38 100644 --- a/rootcause-internals/src/attachment/raw.rs +++ b/rootcause-internals/src/attachment/raw.rs @@ -275,8 +275,6 @@ pub struct RawAttachmentMut<'a> { /// for some `A` using `Box::into_raw`. /// 2. The pointer will point to the same `AttachmentData` for the entire /// lifetime of this object. - /// 3. The pointer provides mutable access to the `AttachmentData` it - /// points to, no other pointers to this data exists except the owner(?). ptr: NonNull>, /// Marker to tell the compiler that we should @@ -321,19 +319,19 @@ impl<'a> RawAttachmentMut<'a> { } } - /// Consumes the mutable reference and returns an immutable one with the - /// same lifetime. + /// Returns a reference to the [`AttachmentData`] instance. #[inline] - pub(super) fn into_ref(self) -> RawAttachmentRef<'a> { + pub fn as_ref<'b: 'a>(&'b self) -> RawAttachmentRef<'b> { RawAttachmentRef { ptr: self.ptr, _marker: PhantomData, } } - /// Returns a reference to the [`AttachmentData`] instance. + /// Consumes the mutable reference and returns an immutable one with the + /// same lifetime. #[inline] - pub fn as_ref<'b: 'a>(&'b self) -> RawAttachmentRef<'b> { + pub(super) fn into_ref(self) -> RawAttachmentRef<'a> { RawAttachmentRef { ptr: self.ptr, _marker: PhantomData, diff --git a/src/report_attachment/mut_.rs b/src/report_attachment/mut_.rs index ef778ac..baaaad3 100644 --- a/src/report_attachment/mut_.rs +++ b/src/report_attachment/mut_.rs @@ -25,10 +25,10 @@ mod limit_field_access { /// 2. If `A` is a `Sized` type: The attachment embedded in the /// [`RawAttachmentRef`] must be of type `A`. raw: RawAttachmentMut<'a>, - _attachment: PhantomData, + _attachment: PhantomData<&'a mut Attachment>, } - impl<'a, A: ?Sized> ReportAttachmentMut<'a, A> { + impl<'a, A: ?Sized + 'static> ReportAttachmentMut<'a, A> { /// Creates a new AttachmentRef from a raw attachment reference /// /// # Safety @@ -51,7 +51,7 @@ mod limit_field_access { /// Returns the underlying raw attachment reference #[must_use] - pub(crate) fn into_raw_ref(self) -> RawAttachmentMut<'a> { + pub(crate) fn into_raw_mut(self) -> RawAttachmentMut<'a> { // We are destroying `self`, so we no longer // need to uphold any safety invariants. self.raw @@ -61,7 +61,7 @@ mod limit_field_access { pub use limit_field_access::ReportAttachmentMut; -impl<'a, A: Sized> ReportAttachmentMut<'a, A> { +impl<'a, A: Sized + 'static> ReportAttachmentMut<'a, A> { /// Obtain the reference to the inner attachment data. /// /// This method provides direct access to the attachment's data when the @@ -99,4 +99,52 @@ impl<'a, A: Sized> ReportAttachmentMut<'a, A> { // 1. Guaranteed by the invariants of this type. unsafe { raw.into_attachment_downcast_unchecked() } } + + /// Changes the context type of the [`ReportAttachmentMut`] to [`Dynamic`]. + /// + /// Calling this method is equivalent to calling `report.into()`, however + /// this method has been restricted to only change the context mode to + /// [`Dynamic`]. + /// + /// This method can be useful to help with type inference or to improve code + /// readability, as it more clearly communicates intent. + /// + /// This method does not actually modify the report in any way. It only has + /// the effect of "forgetting" that the context actually has the + /// type `C`. + /// + /// To get back the report with a concrete `C` you can use the method + /// [`ReportMut::downcast_report`]. + /// + /// # Examples + /// ``` + /// # use rootcause::{prelude::*, ReportMut, markers::Dynamic}; + /// # struct MyError; + /// # let mut report = report!(MyError); + /// let report: ReportMut<'_, MyError> = report.as_mut(); + /// let local_report: ReportMut<'_, Dynamic> = report.into_dynamic(); + /// ``` + #[must_use] + pub fn into_dynamic(self) -> ReportAttachmentMut<'a, Dynamic> { + // SAFETY: + // 1. If `T=Local`, then this is trivially true. If `T=SendSync`, then we are + // not allowed to mutate the returned raw attachmenht in a way that adds + // non-`Send+Sync` objects. We do not mutate the attachment here and the + // invariants of the created `ReportAttachmentMut` guarantee that no such mutation can + // occur in the future either. + let raw = unsafe { self.into_raw_mut() }; + + // SAFETY: + // 1. `C=Dynamic`, so this is trivially true. + // 2. This is guaranteed by the invariants of this type. + // 3. `C=Dynamic`, so this is trivially true. + // 4. This is guaranteed by the invariants of this type. + // 5. This is guaranteed by the invariants of this type. + // 6. This is guaranteed by the invariants of this type. + // 7. This is guaranteed by the invariants of this type. + unsafe { + // @add-unsafe-context: Dynamic + ReportAttachmentMut::::from_raw(raw) + } + } } From 6d1a6e95a941b5368ca6bce9ef77b06a71c1a9c3 Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Sat, 10 Jan 2026 20:16:14 +0100 Subject: [PATCH 3/5] ongoing work --- rootcause-internals/src/attachment/raw.rs | 4 +- rootcause-internals/src/lib.rs | 2 +- src/report_attachment/mut_.rs | 68 +++++++++++++++++++++-- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/rootcause-internals/src/attachment/raw.rs b/rootcause-internals/src/attachment/raw.rs index e1c8d38..989a891 100644 --- a/rootcause-internals/src/attachment/raw.rs +++ b/rootcause-internals/src/attachment/raw.rs @@ -254,7 +254,7 @@ impl<'a> RawAttachmentRef<'a> { } } -/// A lifetime-bound non-shared(?) pointer to an [`AttachmentData`] that is guaranteed to +/// A mutable lifetime-bound pointer to an [`AttachmentData`] that is guaranteed to /// be the sole mutable(?) pointer to an initialized instance of an [`AttachmentData`] for some /// specific `A`, though we do not know which actual `A` it is. /// @@ -309,7 +309,7 @@ impl<'a> RawAttachmentMut<'a> { unsafe { this.as_mut() } } - /// Reborrows the mutable reference to the [`ReportData`] with a shorter + /// Reborrows the mutable reference to the [`AttachmentData`] with a shorter /// lifetime. #[inline] pub fn reborrow<'b>(&'b mut self) -> RawAttachmentMut<'b> { diff --git a/rootcause-internals/src/lib.rs b/rootcause-internals/src/lib.rs index e188890..0e0a5f4 100644 --- a/rootcause-internals/src/lib.rs +++ b/rootcause-internals/src/lib.rs @@ -35,7 +35,7 @@ //! //! - **[`attachment`]**: Type-erased attachment storage //! - [`RawAttachment`]: Owned attachment with [`Box`]-based allocation -//! - [`RawAttachmentRef`]: Borrowed reference to an attachment +//! - [`RawAttachmentRef`]/[`RawAttachmentMut`]: Borrowed reference to an attachment (shared/mutable) //! - [`AttachmentData`]: `#[repr(C)]` wrapper enabling field access on erased //! types //! - [`AttachmentVtable`]: Function pointers for type-erased dispatch diff --git a/src/report_attachment/mut_.rs b/src/report_attachment/mut_.rs index baaaad3..d08b716 100644 --- a/src/report_attachment/mut_.rs +++ b/src/report_attachment/mut_.rs @@ -1,7 +1,10 @@ use alloc::fmt; use core::any::TypeId; -use rootcause_internals::handlers::{AttachmentFormattingStyle, FormattingFunction}; +use rootcause_internals::{ + RawAttachmentMut, + handlers::{AttachmentFormattingStyle, FormattingFunction}, +}; use crate::{markers::Dynamic, util::format_helper}; @@ -23,7 +26,7 @@ mod limit_field_access { /// /// 1. `A` must either be a type bounded by `Sized`, or `Dynamic`. /// 2. If `A` is a `Sized` type: The attachment embedded in the - /// [`RawAttachmentRef`] must be of type `A`. + /// [`RawAttachmentMut`] must be of type `A`. raw: RawAttachmentMut<'a>, _attachment: PhantomData<&'a mut Attachment>, } @@ -62,7 +65,22 @@ mod limit_field_access { pub use limit_field_access::ReportAttachmentMut; impl<'a, A: Sized + 'static> ReportAttachmentMut<'a, A> { - /// Obtain the reference to the inner attachment data. + /// Returns a mutable reference to the current context. + /// + /// # Examples + /// ``` + /// # use rootcause::{prelude::*, ReportMut}; + /// # let mut report: Report = report!("An error occurred".to_string()); + /// let mut report_mut: ReportMut<'_, String> = report.as_mut(); + /// let context: &mut String = report_mut.current_context_mut(); + /// context.push_str(" and that's bad"); + /// ``` + #[must_use] + pub fn inner_mut(&mut self) -> &mut A { + self.as_mut().into_inner_mut() + } + + /// Obtain the mutable reference to the inner attachment data. /// /// This method provides direct access to the attachment's data when the /// concrete type `A` is known at compile time. The attachment type must @@ -92,13 +110,53 @@ impl<'a, A: Sized + 'static> ReportAttachmentMut<'a, A> { /// /// [`downcast_inner`]: Self::downcast_inner #[must_use] - pub fn into_inner(self) -> &'a mut A { - let raw = self.into_raw_ref(); + pub fn into_inner_mut(self) -> &'a mut A { + let raw = self.into_raw_mut(); // SAFETY: // 1. Guaranteed by the invariants of this type. unsafe { raw.into_attachment_downcast_unchecked() } } +} + +impl<'a, A: ?Sized> ReportAttachmentMut<'a, A> { + /// Reborrows the [`ReportAttachmentMut`] to return a new [`ReportAttachmentMut`] with a shorter + /// lifetime + /// + /// # Examples + /// ``` + /// # use rootcause::{prelude::*, ReportMut}; + /// # struct MyError; + /// # let mut report = report!(MyError); + /// let mut report_mut: ReportMut<'_, MyError> = report.as_mut(); + /// { + /// // Create a new mutable reference with a shorter lifetime + /// let mut borrowed_report_mut: ReportMut<'_, MyError> = report_mut.as_mut(); + /// } + /// // After dropping the inner reference report, we can still use the outer one + /// let _context: &MyError = report_mut.current_context(); + /// ``` + #[must_use] + pub fn as_mut(&mut self) -> ReportMut<'_, C, T> { + // SAFETY: + // 1. If `T=Local`, then this is trivially true. If `T=SendSync`, then we are + // not allowed to mutate the returned raw report in a way that adds + // non-`Send+Sync` objects. We do not mutate the report here and the + // invariants of the created `ReportMut` guarantee that no such mutation can + // occur in the future either. + let raw = unsafe { self.as_raw_mut() }; + + // SAFETY: + // 1. This is guaranteed by the invariants of this type. + // 2. This is guaranteed by the invariants of this type. + // 3. If `C` is a `Sized` type: This is guaranteed by the invariants of this + // type. + // 4. This is guaranteed by the invariants of this type. + // 5. This is guaranteed by the invariants of this type. + // 6. If `T = SendSync`: This is guaranteed by the invariants of this type. + // 7. If `T = Local`: This is guaranteed by the invariants of this type. + unsafe { ReportMut::from_raw(raw) } + } /// Changes the context type of the [`ReportAttachmentMut`] to [`Dynamic`]. /// From 33d2340a8310fa2e490a7e528c713cb2be4e25d3 Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Mon, 12 Jan 2026 23:28:13 +0100 Subject: [PATCH 4/5] . --- rootcause-internals/src/attachment/raw.rs | 22 ++- rootcause-internals/src/report/raw.rs | 4 + src/report_attachment/mod.rs | 2 +- src/report_attachment/mut_.rs | 186 ++++++++++++---------- src/report_attachment/owned.rs | 34 +++- src/report_attachment/ref_.rs | 25 ++- 6 files changed, 175 insertions(+), 98 deletions(-) diff --git a/rootcause-internals/src/attachment/raw.rs b/rootcause-internals/src/attachment/raw.rs index 989a891..50a728b 100644 --- a/rootcause-internals/src/attachment/raw.rs +++ b/rootcause-internals/src/attachment/raw.rs @@ -89,6 +89,17 @@ impl RawAttachment { #[inline] pub fn as_ref(&self) -> RawAttachmentRef<'_> { RawAttachmentRef { + // Safety??? + ptr: self.ptr, + _marker: core::marker::PhantomData, + } + } + + /// Returns a mutable reference to the [`AttachmentData`] instance. + #[inline] + pub fn as_mut(&mut self) -> RawAttachmentMut<'_> { + RawAttachmentMut { + // Safety??? ptr: self.ptr, _marker: core::marker::PhantomData, } @@ -275,6 +286,7 @@ pub struct RawAttachmentMut<'a> { /// for some `A` using `Box::into_raw`. /// 2. The pointer will point to the same `AttachmentData` for the entire /// lifetime of this object. + /// 3. This pointer represents exclusive mutable access to the `AttachmentData`. ptr: NonNull>, /// Marker to tell the compiler that we should @@ -323,6 +335,7 @@ impl<'a> RawAttachmentMut<'a> { #[inline] pub fn as_ref<'b: 'a>(&'b self) -> RawAttachmentRef<'b> { RawAttachmentRef { + // Safety??? ptr: self.ptr, _marker: PhantomData, } @@ -331,18 +344,13 @@ impl<'a> RawAttachmentMut<'a> { /// Consumes the mutable reference and returns an immutable one with the /// same lifetime. #[inline] - pub(super) fn into_ref(self) -> RawAttachmentRef<'a> { + pub fn into_ref(self) -> RawAttachmentRef<'a> { RawAttachmentRef { + // Safety??? ptr: self.ptr, _marker: PhantomData, } } - - /// Returns a [`NonNull`] pointer to the [`AttachmentData`] instance. - #[inline] - pub(super) fn into_mut_ptr(self) -> *mut AttachmentData { - self.ptr.as_ptr() - } } #[cfg(test)] diff --git a/rootcause-internals/src/report/raw.rs b/rootcause-internals/src/report/raw.rs index 0cb194a..2c532a7 100644 --- a/rootcause-internals/src/report/raw.rs +++ b/rootcause-internals/src/report/raw.rs @@ -129,6 +129,7 @@ impl RawReport { #[inline] pub unsafe fn as_mut(&mut self) -> RawReportMut<'_> { RawReportMut { + // Safety??? ptr: self.ptr, _marker: core::marker::PhantomData, } @@ -412,6 +413,7 @@ impl<'a> RawReportMut<'a> { #[inline] pub fn reborrow<'b>(&'b mut self) -> RawReportMut<'b> { RawReportMut { + // Safety??? ptr: self.ptr, _marker: core::marker::PhantomData, } @@ -421,6 +423,7 @@ impl<'a> RawReportMut<'a> { #[inline] pub fn as_ref(&self) -> RawReportRef<'_> { RawReportRef { + // Safety??? ptr: self.ptr, _marker: core::marker::PhantomData, } @@ -431,6 +434,7 @@ impl<'a> RawReportMut<'a> { #[inline] pub fn into_ref(self) -> RawReportRef<'a> { RawReportRef { + // Safety??? ptr: self.ptr, _marker: core::marker::PhantomData, } diff --git a/src/report_attachment/mod.rs b/src/report_attachment/mod.rs index c089a5c..cbd9319 100644 --- a/src/report_attachment/mod.rs +++ b/src/report_attachment/mod.rs @@ -157,4 +157,4 @@ mod mut_; mod owned; mod ref_; -pub use self::{owned::ReportAttachment, ref_::ReportAttachmentRef}; +pub use self::{mut_::ReportAttachmentMut, owned::ReportAttachment, ref_::ReportAttachmentRef}; diff --git a/src/report_attachment/mut_.rs b/src/report_attachment/mut_.rs index d08b716..186ec7e 100644 --- a/src/report_attachment/mut_.rs +++ b/src/report_attachment/mut_.rs @@ -6,19 +6,25 @@ use rootcause_internals::{ handlers::{AttachmentFormattingStyle, FormattingFunction}, }; -use crate::{markers::Dynamic, util::format_helper}; +use crate::{ + markers::{Dynamic, SendSync}, + preformatted::PreformattedAttachment, + report_attachment::{ReportAttachment, ReportAttachmentRef}, + util::format_helper, +}; /// FIXME: Once rust-lang/rust#132922 gets resolved, we can make the `raw` field /// an unsafe field and remove this module. mod limit_field_access { use core::marker::PhantomData; - use rootcause_internals::RawAttachmentMut; + use rootcause_internals::{RawAttachmentMut, RawAttachmentRef}; use crate::markers::Dynamic; + /// TODO see [`ReportMut`](crate::report::mut_::ReportMut) #[repr(transparent)] - pub struct ReportAttachmentMut<'a, Attachment: ?Sized + 'static = Dynamic> { + pub struct ReportAttachmentMut<'a, A: ?Sized + 'static = Dynamic> { /// # Safety /// /// The following safety invariants are guaranteed to be upheld as long @@ -27,8 +33,9 @@ mod limit_field_access { /// 1. `A` must either be a type bounded by `Sized`, or `Dynamic`. /// 2. If `A` is a `Sized` type: The attachment embedded in the /// [`RawAttachmentMut`] must be of type `A`. + /// 3. This reference represents exclusive mutable access to the underlying [`AttachmentData`](rootcause_internals::attachment::data::AttachmentData). raw: RawAttachmentMut<'a>, - _attachment: PhantomData<&'a mut Attachment>, + _attachment: PhantomData<&'a mut A>, } impl<'a, A: ?Sized + 'static> ReportAttachmentMut<'a, A> { @@ -46,12 +53,25 @@ mod limit_field_access { // SAFETY: We must uphold the safety invariants of the raw field: // 1. Guaranteed by the caller // 2. Guaranteed by the caller + // 3. Guaranteed by safety invariant #3 of [`RawAttachmentMut`] ReportAttachmentMut { raw, _attachment: PhantomData, } } + // Creates a raw reference to the underlying report. + #[must_use] + pub(crate) fn as_raw_ref<'b>(&'b self) -> RawAttachmentRef<'b> { + // SAFETY: We need to uphold the safety invariants of the raw field: + // 1. Upheld as the type parameter does not change. + // 2. Upheld as the type parameter does not change. + // 3. Upheld since `self` is borrowed as shared and no mutable access can happen through the reference. + let raw: &RawAttachmentMut<'_> = &self.raw; + + raw.as_ref() + } + /// Returns the underlying raw attachment reference #[must_use] pub(crate) fn into_raw_mut(self) -> RawAttachmentMut<'a> { @@ -59,21 +79,46 @@ mod limit_field_access { // need to uphold any safety invariants. self.raw } + + /// Creates a raw reference to the underlying report. + pub(crate) fn as_raw_mut<'b>(&'b mut self) -> RawAttachmentMut<'b> { + // SAFETY: We need to uphold the safety invariants of the raw field: + // 1. Upheld as the type parameter does not change. + // 2. Upheld as the type parameter does not change. + // 3. Upheld since `self` is borrowed mutably transfers exclusive access by futher mutable borrow. + let raw = &mut self.raw; + + raw.reborrow() + } } } pub use limit_field_access::ReportAttachmentMut; impl<'a, A: Sized + 'static> ReportAttachmentMut<'a, A> { - /// Returns a mutable reference to the current context. + /// Returns a reference to the attachment data. /// /// # Examples /// ``` - /// # use rootcause::{prelude::*, ReportMut}; - /// # let mut report: Report = report!("An error occurred".to_string()); - /// let mut report_mut: ReportMut<'_, String> = report.as_mut(); - /// let context: &mut String = report_mut.current_context_mut(); - /// context.push_str(" and that's bad"); + /// # use rootcause::{prelude::*, ReportAttachmentMut}; + /// let mut attachment = ReportAttacment::new(41); + /// let attachment_mut = attachment.as_mut(); + /// let data = attachment_mut.inner(); + /// println!("The answer: {}", *data + 1); // => 42 + /// ``` + #[must_use] + pub fn inner(&self) -> &A { + self.as_ref().inner() + } + + /// Returns a reference to the attachment data. + /// + /// # Examples + /// ``` + /// # use rootcause::{prelude::*, ReportAttachmentMut}; + /// let mut attachment = ReportAttacment::new(41); + /// let attachment_mut = attachment.as_mut(); + /// let data = attachment_mut.inner_mut(); /// ``` #[must_use] pub fn inner_mut(&mut self) -> &mut A { @@ -120,89 +165,66 @@ impl<'a, A: Sized + 'static> ReportAttachmentMut<'a, A> { } impl<'a, A: ?Sized> ReportAttachmentMut<'a, A> { - /// Reborrows the [`ReportAttachmentMut`] to return a new [`ReportAttachmentMut`] with a shorter - /// lifetime + /// Changes the context type of the [`ReportAttachmentMut`] to [`Dynamic`]. /// - /// # Examples - /// ``` - /// # use rootcause::{prelude::*, ReportMut}; - /// # struct MyError; - /// # let mut report = report!(MyError); - /// let mut report_mut: ReportMut<'_, MyError> = report.as_mut(); - /// { - /// // Create a new mutable reference with a shorter lifetime - /// let mut borrowed_report_mut: ReportMut<'_, MyError> = report_mut.as_mut(); - /// } - /// // After dropping the inner reference report, we can still use the outer one - /// let _context: &MyError = report_mut.current_context(); - /// ``` + /// TODO + #[must_use] + pub fn into_dynamic(self) -> ReportAttachmentMut<'a, Dynamic> { + let raw = self.into_raw_mut(); + + // SAFETY: + // 1. Trivially true. + // 2. Not `Sized`. + unsafe { + // @add-unsafe-context: Dynamic + ReportAttachmentMut::::from_raw(raw) + } + } + + /// TODO #[must_use] - pub fn as_mut(&mut self) -> ReportMut<'_, C, T> { + pub fn as_ref(&self) -> ReportAttachmentRef<'_, A> { + let raw = self.as_raw_ref(); + // SAFETY: - // 1. If `T=Local`, then this is trivially true. If `T=SendSync`, then we are - // not allowed to mutate the returned raw report in a way that adds - // non-`Send+Sync` objects. We do not mutate the report here and the - // invariants of the created `ReportMut` guarantee that no such mutation can - // occur in the future either. - let raw = unsafe { self.as_raw_mut() }; + // 1. Guaranteed by invariants of this type. + // 2. Guaranteed by invariants of this type. + unsafe { ReportAttachmentRef::::from_raw(raw) } + } + + /// TODO + #[must_use] + pub fn into_ref(self) -> ReportAttachmentRef<'a, A> { + let raw = self.into_raw_mut(); + + let raw = raw.into_ref(); // SAFETY: - // 1. This is guaranteed by the invariants of this type. - // 2. This is guaranteed by the invariants of this type. - // 3. If `C` is a `Sized` type: This is guaranteed by the invariants of this - // type. - // 4. This is guaranteed by the invariants of this type. - // 5. This is guaranteed by the invariants of this type. - // 6. If `T = SendSync`: This is guaranteed by the invariants of this type. - // 7. If `T = Local`: This is guaranteed by the invariants of this type. - unsafe { ReportMut::from_raw(raw) } + // 1. Guaranteed by invariants of this type. + // 2. Guaranteed by invariants of this type. + unsafe { ReportAttachmentRef::::from_raw(raw) } } - /// Changes the context type of the [`ReportAttachmentMut`] to [`Dynamic`]. - /// - /// Calling this method is equivalent to calling `report.into()`, however - /// this method has been restricted to only change the context mode to - /// [`Dynamic`]. - /// - /// This method can be useful to help with type inference or to improve code - /// readability, as it more clearly communicates intent. - /// - /// This method does not actually modify the report in any way. It only has - /// the effect of "forgetting" that the context actually has the - /// type `C`. - /// - /// To get back the report with a concrete `C` you can use the method - /// [`ReportMut::downcast_report`]. + /// Reborrows the [`ReportAttachmentMut`] to return a new [`ReportAttachmentMut`] with a shorter + /// lifetime /// /// # Examples - /// ``` - /// # use rootcause::{prelude::*, ReportMut, markers::Dynamic}; - /// # struct MyError; - /// # let mut report = report!(MyError); - /// let report: ReportMut<'_, MyError> = report.as_mut(); - /// let local_report: ReportMut<'_, Dynamic> = report.into_dynamic(); - /// ``` + /// + /// TODO, see [`ReportMut::as_mut`](crate::report::mut_::ReportMut::as_mut) #[must_use] - pub fn into_dynamic(self) -> ReportAttachmentMut<'a, Dynamic> { - // SAFETY: - // 1. If `T=Local`, then this is trivially true. If `T=SendSync`, then we are - // not allowed to mutate the returned raw attachmenht in a way that adds - // non-`Send+Sync` objects. We do not mutate the attachment here and the - // invariants of the created `ReportAttachmentMut` guarantee that no such mutation can - // occur in the future either. - let raw = unsafe { self.into_raw_mut() }; + pub fn as_mut(&mut self) -> ReportAttachmentMut<'_, A> { + let raw = self.as_raw_mut(); // SAFETY: - // 1. `C=Dynamic`, so this is trivially true. - // 2. This is guaranteed by the invariants of this type. - // 3. `C=Dynamic`, so this is trivially true. - // 4. This is guaranteed by the invariants of this type. - // 5. This is guaranteed by the invariants of this type. - // 6. This is guaranteed by the invariants of this type. - // 7. This is guaranteed by the invariants of this type. - unsafe { - // @add-unsafe-context: Dynamic - ReportAttachmentMut::::from_raw(raw) - } + // 1. Guaranteed by invariants of this type. + // 2. Guaranteed by invariants of this type. + unsafe { ReportAttachmentMut::from_raw(raw) } + } + + /// TODO see [`crate::report::mut_::ReportMut::preformat`] + #[track_caller] + #[must_use] + pub fn preformat(&self) -> ReportAttachment { + self.as_ref().preformat() } } diff --git a/src/report_attachment/owned.rs b/src/report_attachment/owned.rs index 05137f5..ae2cd43 100644 --- a/src/report_attachment/owned.rs +++ b/src/report_attachment/owned.rs @@ -8,7 +8,8 @@ use rootcause_internals::{ use crate::{ handlers::{self, AttachmentHandler}, markers::{self, Dynamic, Local, SendSync}, - report_attachment::ReportAttachmentRef, + preformatted::PreformattedAttachment, + report_attachment::{ReportAttachmentMut, ReportAttachmentRef}, util::format_helper, }; @@ -17,7 +18,7 @@ use crate::{ mod limit_field_access { use core::marker::PhantomData; - use rootcause_internals::{RawAttachment, RawAttachmentRef}; + use rootcause_internals::{RawAttachment, RawAttachmentMut, RawAttachmentRef}; use crate::markers::{Dynamic, SendSync}; @@ -106,6 +107,19 @@ mod limit_field_access { raw.as_ref() } + + /// Creates a lifetime-bound [`RawAttachmentMut`] from the inner [`RawAttachment`] + #[must_use] + pub(crate) fn as_raw_mut(&mut self) -> RawAttachmentMut<'_> { + // SAFETY: We must uphold the safety invariants of the raw field: + // 1. Upheld as the type parameters do not change. + // 2. Upheld as the type parameters do not change. + // 3. Guaranteed by safety invariant #2 of [`RawAttachmentMut`] + // 4. Guaranteed by safety invariant #2 of [`RawAttachmentMut`] + let raw = &mut self.raw; + + raw.as_mut() + } } } pub use limit_field_access::ReportAttachment; @@ -337,6 +351,22 @@ impl ReportAttachment { // 2. Guaranteed by the invariants of this type. unsafe { ReportAttachmentRef::from_raw(raw) } } + + /// Returns a mutable reference to the attachment. + #[must_use] + pub fn as_mut(&mut self) -> ReportAttachmentMut<'_, A> { + let raw = self.as_raw_mut(); + // SAFETY: + // 1. Guaranteed by the invariants of this type. + // 2. Guaranteed by the invariants of this type. + unsafe { ReportAttachmentMut::from_raw(raw) } + } + + /// TODO + #[must_use] + pub fn preformat(&self) -> ReportAttachment { + self.as_ref().preformat() + } } impl ReportAttachment { diff --git a/src/report_attachment/ref_.rs b/src/report_attachment/ref_.rs index 545f7aa..fa25e85 100644 --- a/src/report_attachment/ref_.rs +++ b/src/report_attachment/ref_.rs @@ -3,7 +3,12 @@ use core::any::TypeId; use rootcause_internals::handlers::{AttachmentFormattingStyle, FormattingFunction}; -use crate::{markers::Dynamic, util::format_helper}; +use crate::{ + markers::{Dynamic, SendSync}, + preformatted::{self, PreformattedAttachment}, + report_attachment::ReportAttachment, + util::format_helper, +}; /// FIXME: Once rust-lang/rust#132922 gets resolved, we can make the `raw` field /// an unsafe field and remove this module. @@ -36,7 +41,7 @@ mod limit_field_access { /// /// [`ReportAttachment`]: crate::report_attachment::ReportAttachment #[repr(transparent)] - pub struct ReportAttachmentRef<'a, Attachment: ?Sized + 'static = Dynamic> { + pub struct ReportAttachmentRef<'a, A: ?Sized + 'static = Dynamic> { /// # Safety /// /// The following safety invariants are guaranteed to be upheld as long @@ -46,7 +51,7 @@ mod limit_field_access { /// 2. If `A` is a `Sized` type: The attachment embedded in the /// [`RawAttachmentRef`] must be of type `A`. raw: RawAttachmentRef<'a>, - _attachment: PhantomData, + _attachment: PhantomData, } impl<'a, A: ?Sized> ReportAttachmentRef<'a, A> { @@ -190,7 +195,7 @@ impl<'a, A: ?Sized> ReportAttachmentRef<'a, A> { /// [`handlers::Display`]: crate::handlers::Display /// [`handlers::Debug`]: crate::handlers::Debug #[must_use] - pub fn inner_handler_type_id(&self) -> TypeId { + pub fn inner_handler_type_id(self) -> TypeId { self.as_raw_ref().attachment_handler_type_id() } @@ -292,7 +297,7 @@ impl<'a, A: ?Sized> ReportAttachmentRef<'a, A> { /// [`Debug`]: core::fmt::Debug #[must_use] pub fn preferred_formatting_style( - &self, + self, report_formatting_function: FormattingFunction, ) -> AttachmentFormattingStyle { crate::hooks::attachment_formatter::get_preferred_formatting_style( @@ -320,12 +325,20 @@ impl<'a, A: ?Sized> ReportAttachmentRef<'a, A> { /// [`preferred_formatting_style`]: Self::preferred_formatting_style #[must_use] pub fn preferred_formatting_style_unhooked( - &self, + self, report_formatting_function: FormattingFunction, ) -> AttachmentFormattingStyle { self.as_raw_ref() .preferred_formatting_style(report_formatting_function) } + + /// TODO [`crate::report::ref_::ReportRef::preformat`] + #[must_use] + pub fn preformat(self) -> ReportAttachment { + ReportAttachment::new_custom::( + PreformattedAttachment::new_from_attachment(self), + ) + } } impl<'a> ReportAttachmentRef<'a, Dynamic> { From 541c71be4ac0367f0a44ea346db9a04e8a213b3b Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Tue, 13 Jan 2026 17:56:51 +0100 Subject: [PATCH 5/5] implement attachment iter mut --- rootcause-internals/src/attachment/data.rs | 2 - src/report/ref_.rs | 10 +- src/report_attachment/mut_.rs | 438 ++++++++++++++++++++- src/report_attachment/owned.rs | 42 ++ src/report_attachment/ref_.rs | 5 +- src/report_attachments/iter.rs | 80 +++- src/report_attachments/mod.rs | 2 +- src/report_attachments/owned.rs | 38 +- 8 files changed, 594 insertions(+), 23 deletions(-) diff --git a/rootcause-internals/src/attachment/data.rs b/rootcause-internals/src/attachment/data.rs index ce99a30..486587f 100644 --- a/rootcause-internals/src/attachment/data.rs +++ b/rootcause-internals/src/attachment/data.rs @@ -19,8 +19,6 @@ //! pointer to `AttachmentData` without constructing an invalid //! reference to the full struct. -use core::any::TypeId; - use crate::{ attachment::{ raw::{RawAttachmentMut, RawAttachmentRef}, diff --git a/src/report/ref_.rs b/src/report/ref_.rs index a750793..a3fa501 100644 --- a/src/report/ref_.rs +++ b/src/report/ref_.rs @@ -6,8 +6,7 @@ use rootcause_internals::handlers::{ContextFormattingStyle, FormattingFunction}; use crate::{ Report, ReportIter, markers::{Cloneable, Dynamic, Local, Mutable, SendSync, Uncloneable}, - preformatted::{self, PreformattedAttachment, PreformattedContext}, - report_attachment::ReportAttachment, + preformatted::{self, PreformattedContext}, report_attachments::ReportAttachments, report_collection::ReportCollection, util::format_helper, @@ -588,12 +587,7 @@ impl<'a, C: ?Sized, O, T> ReportRef<'a, C, O, T> { .collect(), self.attachments() .iter() - .map(|attachment| { - ReportAttachment::new_custom::( - PreformattedAttachment::new_from_attachment(attachment), - ) - .into_dynamic() - }) + .map(|attachment| attachment.preformat().into_dynamic()) .collect(), ) } diff --git a/src/report_attachment/mut_.rs b/src/report_attachment/mut_.rs index 186ec7e..63adffc 100644 --- a/src/report_attachment/mut_.rs +++ b/src/report_attachment/mut_.rs @@ -1,10 +1,7 @@ -use alloc::fmt; use core::any::TypeId; -use rootcause_internals::{ - RawAttachmentMut, - handlers::{AttachmentFormattingStyle, FormattingFunction}, -}; +use alloc::fmt; +use rootcause_internals::handlers::{AttachmentFormattingStyle, FormattingFunction}; use crate::{ markers::{Dynamic, SendSync}, @@ -95,7 +92,7 @@ mod limit_field_access { pub use limit_field_access::ReportAttachmentMut; -impl<'a, A: Sized + 'static> ReportAttachmentMut<'a, A> { +impl<'a, A: Sized> ReportAttachmentMut<'a, A> { /// Returns a reference to the attachment data. /// /// # Examples @@ -221,10 +218,437 @@ impl<'a, A: ?Sized> ReportAttachmentMut<'a, A> { unsafe { ReportAttachmentMut::from_raw(raw) } } - /// TODO see [`crate::report::mut_::ReportMut::preformat`] + /// Returns the [`TypeId`] of the inner attachment. + /// + /// # Examples + /// ``` + /// use std::any::TypeId; + /// + /// use rootcause::{ + /// markers::Dynamic, + /// prelude::*, + /// report_attachment::{ReportAttachment, ReportAttachmentRef}, + /// }; + /// + /// let attachment: ReportAttachment<&str> = ReportAttachment::new("text data"); + /// let attachment: ReportAttachment = attachment.into_dynamic(); + /// let attachment_ref: ReportAttachmentRef<'_, Dynamic> = attachment.as_ref(); + /// assert_eq!(attachment_ref.inner_type_id(), TypeId::of::<&str>()); + /// ``` + #[must_use] + pub fn inner_type_id(&self) -> TypeId { + self.as_raw_ref().attachment_type_id() + } + + /// Returns the [`TypeId`] of the inner attachment. + /// + /// # Examples + /// ``` + /// use std::any::TypeId; + /// + /// use rootcause::{ + /// markers::Dynamic, + /// prelude::*, + /// report_attachment::{ReportAttachment, ReportAttachmentRef}, + /// }; + /// + /// let attachment: ReportAttachment<&str> = ReportAttachment::new("text data"); + /// let attachment: ReportAttachment = attachment.into_dynamic(); + /// let attachment_ref: ReportAttachmentRef<'_, Dynamic> = attachment.as_ref(); + /// assert_eq!( + /// attachment_ref.inner_type_name(), + /// core::any::type_name::<&str>() + /// ); + /// ``` + #[must_use] + pub fn inner_type_name(&self) -> &'static str { + self.as_raw_ref().attachment_type_name() + } + + /// Returns the [`TypeId`] of the handler used when creating this + /// attachment. + /// + /// Each attachment is associated with a specific handler (like + /// [`handlers::Display`] or [`handlers::Debug`]) that determines how it + /// should be formatted when included in a report. This method allows + /// you to inspect which handler is being used. + /// + /// [`handlers::Display`]: crate::handlers::Display + /// [`handlers::Debug`]: crate::handlers::Debug + #[must_use] + pub fn inner_handler_type_id(&self) -> TypeId { + self.as_raw_ref().attachment_handler_type_id() + } + + /// Formats the inner attachment data without applying any formatting hooks. + /// + /// This method provides direct access to the attachment's formatting + /// capabilities as defined by its handler, bypassing any global + /// formatting hooks that might modify the output. The returned object + /// implements both [`Display`] and [`Debug`] traits for flexible + /// formatting options. + /// + /// For formatted output that includes formatting hooks, use + /// [`format_inner`] instead. + /// + /// [`Display`]: core::fmt::Display + /// [`Debug`]: core::fmt::Debug + /// [`format_inner`]: Self::format_inner + #[must_use] + pub fn format_inner_unhooked(&self) -> impl core::fmt::Display + core::fmt::Debug { + format_helper( + self.as_raw_ref(), + |attachment, formatter| attachment.attachment_display(formatter), + |attachment, formatter| attachment.attachment_debug(formatter), + ) + } + + /// Formats the inner attachment data with formatting hooks applied. + /// + /// This method formats the attachment using both its handler and any global + /// formatting hooks that have been registered. The hooks allow for + /// custom formatting behaviors such as filtering, transforming, or + /// decorating the output. The returned object implements both + /// [`Display`] and [`Debug`] traits. + /// + /// For direct formatting without hooks, use [`format_inner_unhooked`] + /// instead. + /// + /// [`Display`]: core::fmt::Display + /// [`Debug`]: core::fmt::Debug + /// [`format_inner_unhooked`]: Self::format_inner_unhooked + #[must_use] + pub fn format_inner(&self) -> impl core::fmt::Display + core::fmt::Debug { + format_helper( + self.as_ref().into_dynamic(), + |attachment, formatter| { + crate::hooks::attachment_formatter::display_attachment(attachment, None, formatter) + }, + |attachment, formatter| { + crate::hooks::attachment_formatter::debug_attachment(attachment, None, formatter) + }, + ) + } + + /// Returns the preferred formatting style for this attachment with + /// formatting hooks applied. + /// + /// This method determines how the attachment should be formatted when + /// included in a report, taking into account both the attachment's + /// handler preferences and any global formatting hooks that might + /// modify the behavior. + /// + /// # Arguments + /// + /// - `report_formatting_function`: Whether the report in which this + /// attachment will be embedded is being formatted using [`Display`] + /// formatting or [`Debug`] + /// + /// [`Display`]: core::fmt::Display + /// [`Debug`]: core::fmt::Debug + #[must_use] + pub fn preferred_formatting_style( + &self, + report_formatting_function: FormattingFunction, + ) -> AttachmentFormattingStyle { + crate::hooks::attachment_formatter::get_preferred_formatting_style( + self.as_ref().into_dynamic(), + report_formatting_function, + ) + } + + /// Returns the preferred formatting style for this attachment without + /// formatting hooks. + /// + /// This method determines how the attachment should be formatted based + /// solely on its handler's preferences, bypassing any global formatting + /// hooks that might modify the behavior. For formatting that includes + /// hooks, use [`preferred_formatting_style`] instead. + /// + /// # Arguments + /// + /// - `report_formatting_function`: Whether the report in which this + /// attachment will be embedded is being formatted using [`Display`] + /// formatting or [`Debug`] + /// + /// [`Display`]: core::fmt::Display + /// [`Debug`]: core::fmt::Debug + /// [`preferred_formatting_style`]: Self::preferred_formatting_style + #[must_use] + pub fn preferred_formatting_style_unhooked( + self, + report_formatting_function: FormattingFunction, + ) -> AttachmentFormattingStyle { + self.as_raw_ref() + .preferred_formatting_style(report_formatting_function) + } + + /// TODO See [`crate::report_attachment::owned::ReportAttachment::preformat`] #[track_caller] #[must_use] pub fn preformat(&self) -> ReportAttachment { self.as_ref().preformat() } } + +impl<'a> ReportAttachmentMut<'a, Dynamic> { + /// Attempts to downcast the attachment reference to a different type `A`. + /// + /// This method performs a safe type cast, returning [`Ok`] if the + /// attachment actually contains data of type `A`, or [`Err`] with the original + /// reference if the types don't match. + /// + /// This method is most useful when going from a [`Dynamic`] to a concrete + /// `A`. + /// + /// # Examples + /// TODO + /// ``` + /// use rootcause::{ + /// markers::Dynamic, + /// prelude::*, + /// report_attachment::{ReportAttachment, ReportAttachmentRef}, + /// }; + /// + /// let attachment: ReportAttachment<&str> = ReportAttachment::new("text data"); + /// let attachment: ReportAttachment = attachment.into_dynamic(); + /// let attachment_ref: ReportAttachmentRef<'_, Dynamic> = attachment.as_ref(); + /// + /// // Try to downcast to the correct type + /// let typed_ref: Option> = attachment_ref.downcast_attachment(); + /// assert!(typed_ref.is_some()); + /// + /// // Try to downcast to an incorrect type + /// let wrong_ref: Option> = attachment_ref.downcast_attachment(); + /// assert!(wrong_ref.is_none()); + /// ``` + #[must_use] + pub fn downcast_attachment(self) -> Result, Self> + where + A: Sized + 'static, + { + if TypeId::of::() == self.inner_type_id() { + // SAFETY: + // 1. We just checked that the types match + let attachment = unsafe { self.downcast_attachment_unchecked() }; + Ok(attachment) + } else { + Err(self) + } + } + + /// Performs an unchecked downcast of the attachment reference to type `A`. + /// + /// This method bypasses type checking and performs the cast without + /// verifying that the attachment actually contains data of type `A`. It + /// is the caller's responsibility to ensure the cast is valid. + /// + /// # Safety + /// + /// The caller must ensure: + /// + /// 1. The inner attachment is actually of type `A`. This can be verified by + /// calling [`inner_type_id()`] first. + /// + /// [`inner_type_id()`]: ReportAttachmentMut::inner_type_id + /// + /// # Examples + /// ``` + /// use rootcause::{ + /// markers::Dynamic, + /// prelude::*, + /// report_attachment::{ReportAttachment, ReportAttachmentRef}, + /// }; + /// + /// let attachment: ReportAttachment<&str> = ReportAttachment::new("text data"); + /// let attachment: ReportAttachment = attachment.into_dynamic(); + /// let attachment_ref: ReportAttachmentRef<'_, Dynamic> = attachment.as_ref(); + /// + /// // SAFETY: We know the attachment contains &str data + /// let typed_ref: ReportAttachmentRef<'_, &str> = + /// unsafe { attachment_ref.downcast_attachment_unchecked() }; + /// ``` + #[must_use] + pub unsafe fn downcast_attachment_unchecked(self) -> ReportAttachmentMut<'a, A> + where + A: Sized + 'static, + { + let raw = self.into_raw_mut(); + + // SAFETY: + // 1. `A` is bounded by `Sized` in the function signature, so this is satisfied. + // 2. Guaranteed by the caller + unsafe { ReportAttachmentMut::from_raw(raw) } + } + + /// Attempts to downcast the inner attachment data to a reference of type + /// `A`. + /// + /// This method performs a safe type cast, returning [`Some`] with a + /// reference to the data if the attachment actually contains data of + /// type `A`, or [`None`] if the types don't match. Unlike + /// [`downcast_attachment`], this method returns a direct reference to + /// the data rather than a [`ReportAttachmentRef`]. + /// + /// # Examples + /// ``` + /// use rootcause::{ + /// markers::Dynamic, + /// prelude::*, + /// report_attachment::{ReportAttachment, ReportAttachmentRef}, + /// }; + /// + /// let attachment: ReportAttachment<&str> = ReportAttachment::new("text data"); + /// let attachment: ReportAttachment = attachment.into_dynamic(); + /// let attachment_ref: ReportAttachmentRef<'_, Dynamic> = attachment.as_ref(); + /// + /// // Try to downcast to the correct type + /// let data: Option<&&str> = attachment_ref.downcast_inner(); + /// assert_eq!(data, Some(&"text data")); + /// + /// // Try to downcast to an incorrect type + /// let wrong_data: Option<&i32> = attachment_ref.downcast_inner(); + /// assert!(wrong_data.is_none()); + /// ``` + /// + /// [`downcast_attachment`]: Self::downcast_attachment + #[must_use] + pub fn downcast_inner(&self) -> Option<&A> + where + A: Sized + 'static, + { + self.as_ref().downcast_inner() + } + + /// Performs an unchecked downcast of the inner attachment data to a + /// reference of type `A`. + /// + /// # Safety + /// + /// The caller must ensure: + /// + /// 1. The inner attachment is actually of type `A`. This can be verified by + /// calling [`inner_type_id()`] first. + /// + /// [`inner_type_id()`]: ReportAttachmentRef::inner_type_id + /// + /// # Examples + /// ``` + /// use rootcause::{ + /// markers::Dynamic, + /// prelude::*, + /// report_attachment::{ReportAttachment, ReportAttachmentRef}, + /// }; + /// + /// let attachment: ReportAttachment<&str> = ReportAttachment::new("text data"); + /// let attachment: ReportAttachment = attachment.into_dynamic(); + /// let attachment_ref: ReportAttachmentRef<'_, Dynamic> = attachment.as_ref(); + /// + /// // SAFETY: We know the attachment contains &str data + /// let data: &&str = unsafe { attachment_ref.downcast_inner_unchecked() }; + /// assert_eq!(*data, "text data"); + /// ``` + #[must_use] + pub unsafe fn downcast_inner_unchecked(&self) -> &A + where + A: Sized + 'static, + { + let ref_ = self.as_ref(); + // SAFETY: + // 1. Guaranteed by the caller + unsafe { ref_.downcast_inner_unchecked() } + } + + /// TODO + #[must_use] + pub fn downcast_inner_mut(&mut self) -> Option<&mut A> + where + A: Sized + 'static, + { + if TypeId::of::() == self.inner_type_id() { + // SAFETY: + // 1. We just checked that the types match + let attachment = unsafe { self.downcast_inner_mut_unchecked() }; + Some(attachment) + } else { + None + } + } + + /// TODO + /// + /// #Safety + /// + /// The caller must ensure: + /// + /// 1. The inner attachment is actually of type `A`. This can be verified by + /// calling [`inner_type_id()`] first. + #[must_use] + pub unsafe fn downcast_inner_mut_unchecked(&mut self) -> &mut A + where + A: Sized + 'static, + { + let raw = self.as_raw_mut(); + + // SAFETY: + // 1. Ensured by the caller. + unsafe { raw.into_attachment_downcast_unchecked() } + } +} + +impl<'a, A: ?Sized> core::fmt::Display for ReportAttachmentMut<'a, A> { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let report: ReportAttachmentRef<'_, Dynamic> = self.as_ref().into_dynamic(); + crate::hooks::attachment_formatter::display_attachment(report, None, formatter) + } +} + +impl<'a, A: ?Sized> core::fmt::Debug for ReportAttachmentMut<'a, A> { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let report: ReportAttachmentRef<'_, Dynamic> = self.as_ref().into_dynamic(); + crate::hooks::attachment_formatter::debug_attachment(report, None, formatter) + } +} + +impl<'a, A: ?Sized> Unpin for ReportAttachmentMut<'a, A> {} + +impl<'a, A: Sized> From> for ReportAttachmentMut<'a, Dynamic> { + fn from(value: ReportAttachmentMut<'a, A>) -> Self { + value.into_dynamic() + } +} + +#[cfg(test)] +mod tests { + use alloc::string::String; + + use super::*; + + #[allow(dead_code)] + struct NonSend(*const ()); + static_assertions::assert_not_impl_any!(NonSend: Send, Sync); + + #[test] + fn report_attachment_mut_is_never_send_sync() { + static_assertions::assert_not_impl_any!(ReportAttachmentMut<'static, ()>: Send, Sync); + static_assertions::assert_not_impl_any!(ReportAttachmentMut<'static, String>: Send, Sync); + static_assertions::assert_not_impl_any!(ReportAttachmentMut<'static, NonSend>: Send, Sync); + static_assertions::assert_not_impl_any!(ReportAttachmentMut<'static, Dynamic>: Send, Sync); + } + + #[test] + fn report_attachment_mut_is_always_unpin() { + static_assertions::assert_impl_all!(ReportAttachmentMut<'static, ()>: Unpin); + static_assertions::assert_impl_all!(ReportAttachmentMut<'static, String>: Unpin); + static_assertions::assert_impl_all!(ReportAttachmentMut<'static, NonSend>: Unpin); + static_assertions::assert_impl_all!(ReportAttachmentMut<'static, Dynamic>: Unpin); + } + + #[test] + fn test_report_mut_is_never_copy_clone() { + static_assertions::assert_not_impl_any!(ReportAttachmentMut<'static, ()>: Copy, Clone); + static_assertions::assert_not_impl_any!(ReportAttachmentMut<'static, String>: Copy, Clone); + static_assertions::assert_not_impl_any!(ReportAttachmentMut<'static, NonSend>: Copy, Clone); + static_assertions::assert_not_impl_any!(ReportAttachmentMut<'static, Dynamic>: Copy, Clone); + } +} diff --git a/src/report_attachment/owned.rs b/src/report_attachment/owned.rs index ae2cd43..a946d0f 100644 --- a/src/report_attachment/owned.rs +++ b/src/report_attachment/owned.rs @@ -362,6 +362,13 @@ impl ReportAttachment { unsafe { ReportAttachmentMut::from_raw(raw) } } + /// Creates a new attachment, with the inner attachment data preformatted. + /// + /// This can be useful, as the preformatted attachment is a newly allocated object + /// and additionally is [`Send`]+[`Sync`]. + /// + /// # Examples + /// /// TODO #[must_use] pub fn preformat(&self) -> ReportAttachment { @@ -469,6 +476,41 @@ impl ReportAttachment { unsafe { raw.attachment_downcast_unchecked() } } + /// Attempts to downcast the inner attachment to a specific type. + /// + /// Returns `Some(&mut A)` if the inner attachment is of type `A`, otherwise + /// returns `None`. + #[must_use] + pub fn downcast_inner_mut(&mut self) -> Option<&mut A> + where + A: Sized + 'static, + { + let mut_ = self.as_mut().downcast_attachment().ok()?; + Some(mut_.into_inner_mut()) + } + + /// Downcasts the inner attachment to a specific type without checking. + /// + /// # Safety + /// + /// The caller must ensure: + /// + /// 1. The inner attachment is actually of type `A` (can be verified by + /// calling [`inner_type_id()`] first) + /// + /// [`inner_type_id()`]: ReportAttachment::inner_type_id + #[must_use] + pub unsafe fn downcast_inner_mut_unchecked(&mut self) -> &mut A + where + A: Sized + 'static, + { + let raw = self.as_raw_mut(); + + // SAFETY: + // 1. Guaranteed by the caller + unsafe { raw.into_attachment_downcast_unchecked() } + } + /// Attempts to downcast the [`ReportAttachment`] to a specific attachment /// type. /// diff --git a/src/report_attachment/ref_.rs b/src/report_attachment/ref_.rs index fa25e85..8a444fd 100644 --- a/src/report_attachment/ref_.rs +++ b/src/report_attachment/ref_.rs @@ -238,9 +238,8 @@ impl<'a, A: ?Sized> ReportAttachmentRef<'a, A> { /// [`format_inner_unhooked`]: Self::format_inner_unhooked #[must_use] pub fn format_inner(self) -> impl core::fmt::Display + core::fmt::Debug { - let attachment: ReportAttachmentRef<'a, Dynamic> = self.into_dynamic(); format_helper( - attachment, + self.into_dynamic(), |attachment, formatter| { crate::hooks::attachment_formatter::display_attachment(attachment, None, formatter) }, @@ -332,7 +331,7 @@ impl<'a, A: ?Sized> ReportAttachmentRef<'a, A> { .preferred_formatting_style(report_formatting_function) } - /// TODO [`crate::report::ref_::ReportRef::preformat`] + /// See [`crate::report_attachment::owned::ReportAttachment::preformat`] #[must_use] pub fn preformat(self) -> ReportAttachment { ReportAttachment::new_custom::( diff --git a/src/report_attachments/iter.rs b/src/report_attachments/iter.rs index ecf9222..9a6c228 100644 --- a/src/report_attachments/iter.rs +++ b/src/report_attachments/iter.rs @@ -4,7 +4,7 @@ use rootcause_internals::RawAttachment; use crate::{ markers::Dynamic, - report_attachment::{ReportAttachment, ReportAttachmentRef}, + report_attachment::{ReportAttachment, ReportAttachmentMut, ReportAttachmentRef}, }; /// An iterator over references to report attachments. @@ -87,6 +87,84 @@ impl<'a> ExactSizeIterator for ReportAttachmentsIter<'a> { impl<'a> FusedIterator for ReportAttachmentsIter<'a> {} +/// An iterator over references to report attachments. +/// +/// This iterator yields [`ReportAttachmentMut`] items and is created by calling +/// [`ReportAttachments::iter_mut`]. +/// +/// [`ReportAttachmentMut`]: crate::report_attachment::ReportAttachmentMut +/// [`ReportAttachments::iter_mut`]: crate::report_attachments::ReportAttachments::iter_mut +/// +/// # Examples +/// TODO +/// ``` +/// use rootcause::{ +/// report_attachment::ReportAttachment, +/// report_attachments::{ReportAttachments, ReportAttachmentsIter}, +/// }; +/// +/// let mut attachments = ReportAttachments::new_sendsync(); +/// attachments.push(ReportAttachment::new("debug info").into_dynamic()); +/// attachments.push(ReportAttachment::new(42).into_dynamic()); +/// +/// let iterator: ReportAttachmentsIter<'_> = attachments.iter(); +/// ``` +#[must_use] +pub struct ReportAttachmentsIterMut<'a> { + raw: core::slice::IterMut<'a, RawAttachment>, +} + +impl<'a> ReportAttachmentsIterMut<'a> { + /// Creates a new `AttachmentsIter` from an iterator of raw attachments + pub(crate) fn from_raw(raw: core::slice::IterMut<'a, RawAttachment>) -> Self { + Self { raw } + } +} + +impl<'a> Iterator for ReportAttachmentsIterMut<'a> { + type Item = ReportAttachmentMut<'a, Dynamic>; + + fn next(&mut self) -> Option { + let raw = self.raw.next()?.as_mut(); + + // SAFETY: + // 1. `A = Dynamic`, so this is trivially satisfied. + // 2. `A = Dynamic`, so this is trivially satisfied. + let attachment = unsafe { + // @add-unsafe-context: Dynamic + ReportAttachmentMut::<'a, Dynamic>::from_raw(raw) + }; + + Some(attachment) + } + + fn size_hint(&self) -> (usize, Option) { + self.raw.size_hint() + } +} + +impl<'a> ExactSizeIterator for ReportAttachmentsIterMut<'a> { + fn len(&self) -> usize { + self.raw.len() + } +} + +impl<'a> DoubleEndedIterator for ReportAttachmentsIterMut<'a> { + fn next_back(&mut self) -> Option { + let raw = self.raw.next_back()?.as_mut(); + + // SAFETY: + // 1. `A = Dynamic`, so this is trivially satisfied. + // 2. `A = Dynamic`, so this is trivially satisfied. + let attachment = unsafe { + // @add-unsafe-context: Dynamic + ReportAttachmentMut::<'a, Dynamic>::from_raw(raw) + }; + + Some(attachment) + } +} + /// FIXME: Once rust-lang/rust#132922 gets resolved, we can make the `raw` field /// an unsafe field and remove this module. mod limit_field_access { diff --git a/src/report_attachments/mod.rs b/src/report_attachments/mod.rs index 879a316..86aca9e 100644 --- a/src/report_attachments/mod.rs +++ b/src/report_attachments/mod.rs @@ -40,6 +40,6 @@ mod iter; mod owned; pub use self::{ - iter::{ReportAttachmentsIntoIter, ReportAttachmentsIter}, + iter::{ReportAttachmentsIntoIter, ReportAttachmentsIter, ReportAttachmentsIterMut}, owned::ReportAttachments, }; diff --git a/src/report_attachments/owned.rs b/src/report_attachments/owned.rs index 5dd13d0..ce77928 100644 --- a/src/report_attachments/owned.rs +++ b/src/report_attachments/owned.rs @@ -3,7 +3,9 @@ use alloc::vec::Vec; use crate::{ markers::{Dynamic, Local, SendSync}, report_attachment::{ReportAttachment, ReportAttachmentRef}, - report_attachments::{ReportAttachmentsIntoIter, ReportAttachmentsIter}, + report_attachments::{ + ReportAttachmentsIntoIter, ReportAttachmentsIter, ReportAttachmentsIterMut, + }, }; /// FIXME: Once rust-lang/rust#132922 gets resolved, we can make the `raw` field @@ -355,6 +357,40 @@ impl ReportAttachments { pub fn iter(&self) -> ReportAttachmentsIter<'_> { ReportAttachmentsIter::from_raw(self.as_raw().iter()) } + /// Returns an iterator over references to the attachments in the + /// collection. + /// + /// The iterator yields [`ReportAttachmentMut`] items, which provide + /// non-owning mutable access to the attachments. For owning iteration, use + /// [`into_iter()`] instead. + /// + /// # Examples + /// TODO + /// ``` + /// use rootcause::{report_attachment::ReportAttachment, report_attachments::ReportAttachments}; + /// + /// let mut attachments = ReportAttachments::new_sendsync(); + /// attachments.push(ReportAttachment::new("first").into_dynamic()); + /// attachments.push(ReportAttachment::new("second").into_dynamic()); + /// + /// for attachment in attachments.iter() { + /// println!("Attachment type: {:?}", attachment.inner_type_id()); + /// } + /// ``` + /// + /// [`into_iter()`]: Self::into_iter + pub fn iter_mut(&mut self) -> ReportAttachmentsIterMut<'_> { + // SAFETY: + // + // 1. Mutation of the collection is not possible through the iterator. + // 2. Mutation of the individual attachments are only possible in a type-preserving manner, + // meaning it is not possible to alter any of the attachments to be non-`Send + Sync` + let raw = unsafe { + // @add-unsafe-context: ??? + self.as_raw_mut() + }; + ReportAttachmentsIterMut::from_raw(raw.iter_mut()) + } /// Converts this collection to use the [`Local`] thread safety marker. ///