From 623935c51633a8c46a84d8f56f98f391d44feed5 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 4 Sep 2025 19:15:51 -0700 Subject: [PATCH 01/26] Introduce MovingPtr --- crates/bevy_ptr/src/lib.rs | 213 ++++++++++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 15a193d737530..0d3e9fde8ca5d 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -11,7 +11,7 @@ use core::{ cell::UnsafeCell, fmt::{self, Debug, Formatter, Pointer}, marker::PhantomData, - mem::ManuallyDrop, + mem::{self, ManuallyDrop, MaybeUninit}, num::NonZeroUsize, ptr::{self, NonNull}, }; @@ -26,11 +26,20 @@ pub struct Unaligned; /// Trait that is only implemented for [`Aligned`] and [`Unaligned`] to work around the lack of ability /// to have const generics of an enum. -pub trait IsAligned: sealed::Sealed {} +pub trait IsAligned: sealed::Sealed { + #[doc(hidden)] + const IS_ALIGNED: bool; +} -impl IsAligned for Aligned {} +impl IsAligned for Aligned { + #[doc(hidden)] + const IS_ALIGNED: bool = true; +} -impl IsAligned for Unaligned {} +impl IsAligned for Unaligned { + #[doc(hidden)] + const IS_ALIGNED: bool = false; +} mod sealed { pub trait Sealed {} @@ -151,6 +160,7 @@ impl<'a, T: ?Sized> From<&'a mut T> for ConstNonNull { ConstNonNull(NonNull::from(value)) } } + /// Type-erased borrow of some unknown type chosen when constructing this type. /// /// This type tries to act "borrow-like" which means that: @@ -198,6 +208,23 @@ pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut #[repr(transparent)] pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut u8, A)>); +/// A Box-like pointer for moving a value to a new memory location without needing to pass by +/// value. +/// +/// Conceptually represents ownership of whatever data is being pointed to and will call its +/// `Drop` impl upon being dropped. This pointer is _not_ responsible for freeing +/// the memory pointed to by this pointer as it may be pointing to an element in a `Vec` or +/// to a local in a function etc. +/// +/// This type tries to act "borrow-like" like which means that: +/// - Pointer should be considered exclusive and mutable. It cannot be cloned as this would lead +/// to aliased mutability and potentially use after free bugs. +/// - It must always points to a valid value of whatever the pointee type is. +/// - The lifetime `'a` accurately represents how long the pointer is valid for. +/// - It does not support pointer arithmetic in any way. +/// - Must be sufficiently aligned for the pointee type. +pub struct MovingPtr<'a, T, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut T, A)>); + macro_rules! impl_ptr { ($ptr:ident) => { impl<'a> $ptr<'a, Aligned> { @@ -272,6 +299,7 @@ macro_rules! impl_ptr { write!(f, "{}({:?})", stringify!($ptr), self.0) } } + impl Debug for $ptr<'_, Unaligned> { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -285,6 +313,183 @@ impl_ptr!(Ptr); impl_ptr!(PtrMut); impl_ptr!(OwningPtr); +impl<'a, T> MovingPtr<'a, T, Aligned> { + /// Removes the alignment requirement of this pointer + pub fn to_unaligned(self) -> MovingPtr<'a, T, Unaligned> { + let value = MovingPtr(self.0, PhantomData); + mem::forget(self); + value + } +} + +impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { + /// Creates a new instance from a raw pointer. + /// + /// # Safety + /// - `inner` must point to valid value of `T`. + /// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for `T`. + /// - `inner` must have correct provenance to allow read and writes of the pointee type. + /// - The lifetime `'a` must be constrained such that this [`MovingPtr`] will stay valid and nothing + /// else can read or mutate the pointee while this [`MovingPtr`] is live. + #[inline] + pub unsafe fn new(inner: NonNull) -> Self { + Self(inner, PhantomData) + } + + /// This exists mostly to reduce compile times; + /// code is only duplicated per type, rather than per function called. + fn make_internal(temp: &mut ManuallyDrop) -> MovingPtr<'_, T> { + MovingPtr(NonNull::from(temp).cast::(), PhantomData) + } + + /// Consumes a value and creates an [`MovingPtr`] to it while ensuring a double drop does not happen. + #[inline] + pub fn make) -> R, R>(val: T, f: F) -> R { + let mut val = ManuallyDrop::new(val); + // SAFETY: The value behind the pointer will not get dropped or observed later, + // so it's safe to promote it to an owning pointer. + f(Self::make_internal(&mut val)) + } + + /// Partially moves out some fields inside of `self`. + /// + /// The partially returned value is returned back pointing to `MaybeUninit`. + /// + /// # Safety + /// The fields moved out of in `f` must not be accessed or dropped after this function returns. + #[inline] + pub unsafe fn partial_move( + ptr: MovingPtr<'a, T, A>, + f: impl FnOnce(MovingPtr<'a, T, A>) -> MovingPtr<'a, T, A>, + ) -> MovingPtr<'a, MaybeUninit, A> { + let value = f(ptr); + let partial = MovingPtr(value.0.cast::>(), PhantomData); + mem::forget(value); + partial + } + + /// Reads the value pointed to by this pointer. + #[inline] + pub fn read(self) -> T { + let value = if const { A::IS_ALIGNED } { + unsafe { self.0.as_ptr().read() } + } else { + unsafe { self.0.as_ptr().read_unaligned() } + }; + mem::forget(self); + value + } + + /// Writes the value pointed to by this pointer to a provided location. + /// + /// This does *not* drop the value stored at `dst` and it's the caller's responsibility + /// to ensure that it's properly dropped. + /// + /// # Safety + /// - `dst` must be valid for writes. + /// - If the `A` type parameter is [`Aligned`] then `dst` must be properly aligned for `T`. + #[inline] + pub unsafe fn write_to(self, dst: *mut T) { + let src = self.0.as_ptr(); + mem::forget(self); + if const { A::IS_ALIGNED } { + // SAFETY: + // - `src` must be valid for reads as this pointer is considered to own the value it points to. + // - The caller is required to ensure that `dst` must be valid for writes. + // - `A::IS_ALIGNED` so `src` must be aligned to `T`. + // - `A::IS_ALIGNED` so the caller must ensure that `dst` must be aligned to `T`. + // - As this pointer is considered to own the value it points to, and both must be aligned, + // so both regions of memory cannot overlap. + unsafe { + ptr::copy_nonoverlapping(src, dst, 1); + } + } else { + // SAFETY: + // - `src` must be valid for reads as this pointer is considered to own the value it points to. + // - The caller is required to ensure that `dst` must be valid for writes. + // - As `!A::IS_ALIGNED`, a bytewise copy is performed, which is guaranteed to be aligned, + // even if `src` and `dst` are not aligned for `T`. + unsafe { + ptr::copy::(src.cast::(), dst.cast::(), size_of::()); + } + }; + } + + /// Creates a [`MovingPtr`] for a specific field within `self`. + /// + /// The correct byte_offset for a field can be obtained via [`core::mem::offset_of`]. + /// + /// # Safety + /// - `U` must be the correct type for the field at `byte_offset` within `self`. + /// - `self` should not be accesesed or dropped as if it were a complete value. + /// Other fields that have not been moved out of may still be accessed or dropped separately. + #[inline] + pub unsafe fn move_field(&self, byte_offset: usize) -> MovingPtr<'a, U, A> { + MovingPtr( + unsafe { self.0.byte_add(byte_offset) }.cast::(), + PhantomData, + ) + } +} + +impl<'a, T, A: IsAligned> MovingPtr<'a, MaybeUninit, A> { + /// Creates a [`MovingPtr`] pointing to a valid instance of `T`. + /// + /// See also: [`MaybeUninit::assume_init`]. + /// + /// # Safety + /// It's up to the caller to ensure that the value pointed to by `self` + /// is really in an initialized state. Calling this when the content is not yet + /// fully initialized causes immediate undefined behavior. + #[inline] + pub unsafe fn assume_init(self) -> MovingPtr<'a, T, A> { + let value = MovingPtr(self.0.cast::(), PhantomData); + mem::forget(self); + value + } +} + +impl Pointer for MovingPtr<'_, T, A> { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Pointer::fmt(&self.0, f) + } +} + +impl Debug for MovingPtr<'_, T, Aligned> { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}({:?})", stringify!($ptr), self.0) + } +} + +impl Debug for MovingPtr<'_, T, Unaligned> { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}({:?})", stringify!($ptr), self.0) + } +} + +impl<'a, T, A: IsAligned> From> for OwningPtr<'a, A> { + fn from(value: MovingPtr<'a, T, A>) -> Self { + let ptr = unsafe { OwningPtr::new(value.0.cast::()) }; + mem::forget(value); + ptr + } +} + +impl Drop for MovingPtr<'_, T, A> { + fn drop(&mut self) { + if const { A::IS_ALIGNED } { + // SAFETY: If `A::IS_ALIGNED` the value pointed to by this pointer + // must be aligned. + unsafe { ptr::drop_in_place(self.0.as_ptr()) }; + } else { + drop(unsafe { self.0.as_ptr().read_unaligned() }); + } + } +} + impl<'a, A: IsAligned> Ptr<'a, A> { /// Creates a new instance from a raw pointer. /// From a33001c6688acee7185c23e55b83405c0952ab6b Mon Sep 17 00:00:00 2001 From: James Liu Date: Fri, 5 Sep 2025 03:14:14 +0000 Subject: [PATCH 02/26] Apply suggestions from code review Co-authored-by: Alice Cecile --- crates/bevy_ptr/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 0d3e9fde8ca5d..f0bb89951e70c 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -208,11 +208,11 @@ pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut #[repr(transparent)] pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut u8, A)>); -/// A Box-like pointer for moving a value to a new memory location without needing to pass by +/// A [`Box`]-like pointer for moving a value to a new memory location without needing to pass by /// value. /// /// Conceptually represents ownership of whatever data is being pointed to and will call its -/// `Drop` impl upon being dropped. This pointer is _not_ responsible for freeing +/// [`Drop`] impl upon being dropped. This pointer is _not_ responsible for freeing /// the memory pointed to by this pointer as it may be pointing to an element in a `Vec` or /// to a local in a function etc. /// From 0e04b099b7d4f2a8f1f925835446cae9c07d65e6 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 00:50:24 -0700 Subject: [PATCH 03/26] Provide doc tests, better safety comments, and use associated functions instead of constants --- crates/bevy_ptr/src/lib.rs | 308 +++++++++++++++++++++++++++++-------- 1 file changed, 247 insertions(+), 61 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index f0bb89951e70c..7ddb5aeeb7164 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -27,18 +27,105 @@ pub struct Unaligned; /// Trait that is only implemented for [`Aligned`] and [`Unaligned`] to work around the lack of ability /// to have const generics of an enum. pub trait IsAligned: sealed::Sealed { + /// Reads the value pointed to by `ptr`. + /// + /// # Safety + /// - `ptr` must be valid for reads. + /// - `ptr` must point to a valid instance of type `T` + /// - If this type is [`Aligned`], then `ptr` must be properly aligned for type `T`. + #[doc(hidden)] + unsafe fn read_ptr(ptr: *const T) -> T; + #[doc(hidden)] - const IS_ALIGNED: bool; + unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, size: usize); + + /// Reads the value pointed to by `ptr`. + /// + /// # Safety + /// - `ptr` must be valid for reads and writes. + /// - `ptr` must point to a valid instance of type `T` + /// - If this type is [`Aligned`], then `ptr` must be properly aligned for type `T`. + /// - The value pointed to by `ptr` must be valid for dropping. + /// - While `drop_in_place` is executing, the only way to access parts of `ptr` is through + /// the `&mut Self` supplied to it's `Drop::drop` impl. + #[doc(hidden)] + unsafe fn drop_in_place(ptr: *mut T); } impl IsAligned for Aligned { - #[doc(hidden)] - const IS_ALIGNED: bool = true; + #[inline] + unsafe fn read_ptr(ptr: *const T) -> T { + // SAFETY: + // - The caller is required to ensure that `src` must be valid for reads. + // - The caller is required to ensure that `src` points to a valid instance of type `T`. + // - This type is `Aligned` so the caller must ensure that `src` is properly aligned for type `T`. + unsafe { ptr.read() } + } + + #[inline] + unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize) { + // SAFETY: + // - The caller is required to ensure that `src` must be valid for reads. + // - The caller is required to ensure that `dst` must be valid for writes. + // - The caller is required to ensure that `src` and `dst` are aligned. + // - The caller is required to ensure that the memory region covered by `src` + // and `dst`, fitting up to `count` elements do not overlap. + unsafe { + ptr::copy_nonoverlapping(src, dst, count); + } + } + + #[inline] + unsafe fn drop_in_place(ptr: *mut T) { + // SAFETY: + // - The caller is required to ensure that `ptr` must be valid for reads and writes. + // - The caller is required to ensure that `ptr` points to a valid instance of type `T`. + // - This type is `Aligned` so the caller must ensure that `ptr` is properly aligned for type `T`. + // - The caller is required to ensure that `ptr` points must be valid for dropping. + // - The caller is required to ensure that the value `ptr` points must not be used after this function + // call. + unsafe { ptr::drop_in_place(ptr) }; + } } impl IsAligned for Unaligned { - #[doc(hidden)] - const IS_ALIGNED: bool = false; + #[inline] + unsafe fn read_ptr(ptr: *const T) -> T { + // SAFETY: + // - The caller is required to ensure that `src` must be valid for reads. + // - The caller is required to ensure that `src` points to a valid instance of type `T`. + unsafe { ptr.read_unaligned() } + } + + #[inline] + unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize) { + // SAFETY: + // - The caller is required to ensure that `src` must be valid for reads. + // - The caller is required to ensure that `dst` must be valid for writes. + // - This is doing a byte-wise copy. `src` and `dst` are always guaranteed to be + // aligned. + // - The caller is required to ensure that the memory region covered by `src` + // and `dst`, fitting up to `count` elements do not overlap. + unsafe { + ptr::copy_nonoverlapping::( + src.cast::(), + dst.cast::(), + count * size_of::(), + ); + } + } + + #[inline] + unsafe fn drop_in_place(ptr: *mut T) { + // SAFETY: + // - The caller is required to ensure that `ptr` must be valid for reads and writes. + // - The caller is required to ensure that `ptr` points to a valid instance of type `T`. + // - This type is not `Aligned` so the caller does not need to ensure that `ptr` is properly aligned for type `T`. + // - The caller is required to ensure that `ptr` points must be valid for dropping. + // - The caller is required to ensure that the value `ptr` points must not be used after this function + // call. + unsafe { drop(ptr.read_unaligned()) } + } } mod sealed { @@ -223,6 +310,10 @@ pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a m /// - The lifetime `'a` accurately represents how long the pointer is valid for. /// - It does not support pointer arithmetic in any way. /// - Must be sufficiently aligned for the pointee type. +/// +/// If the generic type parameter `A` is [`Aligned`] (the default), then all instances of the type +/// must be properly aligned for type `T`. +#[repr(transparent)] pub struct MovingPtr<'a, T, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut T, A)>); macro_rules! impl_ptr { @@ -320,21 +411,6 @@ impl<'a, T> MovingPtr<'a, T, Aligned> { mem::forget(self); value } -} - -impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { - /// Creates a new instance from a raw pointer. - /// - /// # Safety - /// - `inner` must point to valid value of `T`. - /// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for `T`. - /// - `inner` must have correct provenance to allow read and writes of the pointee type. - /// - The lifetime `'a` must be constrained such that this [`MovingPtr`] will stay valid and nothing - /// else can read or mutate the pointee while this [`MovingPtr`] is live. - #[inline] - pub unsafe fn new(inner: NonNull) -> Self { - Self(inner, PhantomData) - } /// This exists mostly to reduce compile times; /// code is only duplicated per type, rather than per function called. @@ -350,32 +426,94 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { // so it's safe to promote it to an owning pointer. f(Self::make_internal(&mut val)) } +} + +impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { + /// Creates a new instance from a raw pointer. + /// + /// # Safety + /// - `inner` must point to valid value of `T`. + /// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for `T`. + /// - `inner` must have correct provenance to allow read and writes of the pointee type. + /// - The lifetime `'a` must be constrained such that this [`MovingPtr`] will stay valid and nothing + /// else can read or mutate the pointee while this [`MovingPtr`] is live. + #[inline] + pub unsafe fn new(inner: NonNull) -> Self { + Self(inner, PhantomData) + } /// Partially moves out some fields inside of `self`. /// /// The partially returned value is returned back pointing to `MaybeUninit`. /// /// # Safety - /// The fields moved out of in `f` must not be accessed or dropped after this function returns. + /// - The call into `f` must not complete having dropped the provided pointer. + /// - The fields moved out of in `f` must not be accessed or dropped after this function returns. + /// + /// As a result, it is strongly recommended to call [`forget`] on the provided pointer once the + /// partial deconstruction has completed. + /// + /// # Example + /// + /// ``` + /// use core::mem::{offset_of, MaybeUninit}; + /// use bevy_ptr::MovingPtr; + /// # use bevy_ptr::Unaligned; + /// # struct FieldAType(usize); + /// # struct FieldBType(usize); + /// # struct FieldCType(usize); + /// # fn insert(_ptr: MovingPtr<'_, T, Unaligned>) {} + /// + /// struct Parent { + /// field_a: FieldAType, + /// field_b: FieldBType, + /// field_c: FieldCType, + /// } + /// + /// # let parent = Parent { + /// # field_a: FieldAType(0), + /// # field_b: FieldBType(0), + /// # field_c: FieldCType(0), + /// # }; + /// + /// MovingPtr::make(parent, |parent_ptr| unsafe { + /// // SAFETY: + /// // - It is impossible for the provided closure to drop the provided pointer as `move_field` cannot panic. + /// // - `field_a` and `field_b` are moved out of but never accessed after this. + /// let partial_parent = MovingPtr::partial_move(parent_ptr, |parent_ptr| { + /// let field_a = parent_ptr.move_field::(offset_of!(Parent, field_a)); + /// let field_b = parent_ptr.move_field::(offset_of!(Parent, field_b)); + /// // Each call to insert may panic! Ensure that `parent_ptr` cannot be dropped before + /// // calling them! + /// core::mem::forget(parent_ptr); + /// insert(field_a); + /// insert(field_b); + /// }); + /// + /// // Move the rest of fields out of the parent. + /// // SAFETY: The fields were not moved out of in the above `partial_move` and thus must still be valid. + /// let field_c = partial_parent.move_field::>(offset_of!(Parent, field_c)); + /// insert(unsafe { field_c.assume_init() }); + /// }); + /// ``` #[inline] pub unsafe fn partial_move( ptr: MovingPtr<'a, T, A>, - f: impl FnOnce(MovingPtr<'a, T, A>) -> MovingPtr<'a, T, A>, + f: impl FnOnce(MovingPtr<'a, T, A>), ) -> MovingPtr<'a, MaybeUninit, A> { - let value = f(ptr); - let partial = MovingPtr(value.0.cast::>(), PhantomData); - mem::forget(value); - partial + let partial_ptr = ptr.0; + f(ptr); + MovingPtr(partial_ptr.cast::>(), PhantomData) } /// Reads the value pointed to by this pointer. #[inline] pub fn read(self) -> T { - let value = if const { A::IS_ALIGNED } { - unsafe { self.0.as_ptr().read() } - } else { - unsafe { self.0.as_ptr().read_unaligned() } - }; + // SAFETY: + // - `self.0` must be valid for reads as this type owns the value it points to. + // - `self.0` must always point to a valid instance of type `T` + // - If `A` is [`Aligned`], then `ptr` must be properly aligned for type `T`. + let value = unsafe { A::read_ptr(self.0.as_ptr()) }; mem::forget(self); value } @@ -392,40 +530,73 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { pub unsafe fn write_to(self, dst: *mut T) { let src = self.0.as_ptr(); mem::forget(self); - if const { A::IS_ALIGNED } { - // SAFETY: - // - `src` must be valid for reads as this pointer is considered to own the value it points to. - // - The caller is required to ensure that `dst` must be valid for writes. - // - `A::IS_ALIGNED` so `src` must be aligned to `T`. - // - `A::IS_ALIGNED` so the caller must ensure that `dst` must be aligned to `T`. - // - As this pointer is considered to own the value it points to, and both must be aligned, - // so both regions of memory cannot overlap. - unsafe { - ptr::copy_nonoverlapping(src, dst, 1); - } - } else { - // SAFETY: - // - `src` must be valid for reads as this pointer is considered to own the value it points to. - // - The caller is required to ensure that `dst` must be valid for writes. - // - As `!A::IS_ALIGNED`, a bytewise copy is performed, which is guaranteed to be aligned, - // even if `src` and `dst` are not aligned for `T`. - unsafe { - ptr::copy::(src.cast::(), dst.cast::(), size_of::()); - } - }; + // SAFETY: + // - `src` must be valid for reads as this pointer is considered to own the value it points to. + // - The caller is required to ensure that `dst` must be valid for writes. + // - As `A` is `Aligned`, the caller is required to ensure that `dst` is aligned and `src` must + // be aligned by the type's invariants. + unsafe { A::copy_nonoverlapping(src, dst, 1) }; } /// Creates a [`MovingPtr`] for a specific field within `self`. /// - /// The correct byte_offset for a field can be obtained via [`core::mem::offset_of`]. + /// This function is expliciitly made for deconstructive moves. + /// + /// The correct `byte_offset` for a field can be obtained via [`core::mem::offset_of`]. + /// + /// The returned value will always be considered unaligned as `repr(packed)` types may result in + /// unaligned fields. The pointer is convertible back into an aligned one using the [`TryFrom`] impl. /// /// # Safety /// - `U` must be the correct type for the field at `byte_offset` within `self`. /// - `self` should not be accesesed or dropped as if it were a complete value. /// Other fields that have not been moved out of may still be accessed or dropped separately. + /// - This function cannot alias the field with any other access, including other calls to [`move_field`] + /// for the same field, without first calling [`forget`] on it first. + /// + /// A result of the above invariants means that any operation that could cause `self` to be dropped while + /// the pointers to the fields are held will result in undefined behavior. This requires exctra caution + /// around code that may panic. See the example below for an example of how to safely use this function. + /// + /// # Example + /// + /// ``` + /// use core::mem::offset_of; + /// use bevy_ptr::MovingPtr; + /// # use bevy_ptr::Unaligned; + /// # struct FieldAType(usize); + /// # struct FieldBType(usize); + /// # struct FieldCType(usize); + /// # fn insert(_ptr: MovingPtr<'_, T, Unaligned>) {} + /// + /// struct Parent { + /// field_a: FieldAType, + /// field_b: FieldBType, + /// field_c: FieldCType, + /// } + /// + /// # let parent = Parent { + /// # field_a: FieldAType(0), + /// # field_b: FieldBType(0), + /// # field_c: FieldCType(0), + /// # }; + /// + /// MovingPtr::make(parent, |parent_ptr| unsafe { + /// let field_a = parent_ptr.move_field::(offset_of!(Parent, field_a)); + /// let field_b = parent_ptr.move_field::(offset_of!(Parent, field_b)); + /// let field_c = parent_ptr.move_field::(offset_of!(Parent, field_c)); + /// // Each call to insert may panic! Ensure that `parent_ptr` cannot be dropped before + /// // calling them! + /// core::mem::forget(parent_ptr); + /// insert(field_a); + /// insert(field_b); + /// insert(field_c); + /// }); + /// ``` #[inline] - pub unsafe fn move_field(&self, byte_offset: usize) -> MovingPtr<'a, U, A> { + pub unsafe fn move_field(&self, byte_offset: usize) -> MovingPtr<'a, U, Unaligned> { MovingPtr( + // SAFETY: The caller must ensure that `U` is the correct type for the field at `byte_offset`. unsafe { self.0.byte_add(byte_offset) }.cast::(), PhantomData, ) @@ -471,6 +642,7 @@ impl Debug for MovingPtr<'_, T, Unaligned> { } impl<'a, T, A: IsAligned> From> for OwningPtr<'a, A> { + #[inline] fn from(value: MovingPtr<'a, T, A>) -> Self { let ptr = unsafe { OwningPtr::new(value.0.cast::()) }; mem::forget(value); @@ -478,18 +650,32 @@ impl<'a, T, A: IsAligned> From> for OwningPtr<'a, A> { } } -impl Drop for MovingPtr<'_, T, A> { - fn drop(&mut self) { - if const { A::IS_ALIGNED } { - // SAFETY: If `A::IS_ALIGNED` the value pointed to by this pointer - // must be aligned. - unsafe { ptr::drop_in_place(self.0.as_ptr()) }; +impl<'a, T> TryFrom> for MovingPtr<'a, T, Aligned> { + type Error = Unaligned; + #[inline] + fn try_from(value: MovingPtr<'a, T, Unaligned>) -> Result { + let ptr = value.0; + mem::forget(value); + if ptr.as_ptr().is_aligned() { + Ok(MovingPtr(ptr, PhantomData)) } else { - drop(unsafe { self.0.as_ptr().read_unaligned() }); + Err(Unaligned) } } } +impl Drop for MovingPtr<'_, T, A> { + fn drop(&mut self) { + // SAFETY: + // - `self.0` must be valid for reads and writes as this pointer type owns the value it points to. + // - `self.0` must always point to a valid instance of type `T` + // - If `A` is `Aligned`, then `ptr` must be properly aligned for type `T` by construction. + // - `self.0` owns the value it points to so it must always be valid for dropping until this pointer is dropped. + // - This type owns the value it points to, so it's required to not mutably alias value that it points to. + unsafe { A::drop_in_place(self.0.as_ptr()) }; + } +} + impl<'a, A: IsAligned> Ptr<'a, A> { /// Creates a new instance from a raw pointer. /// From 8eb48236689f707a5230c982716659b132f34c5e Mon Sep 17 00:00:00 2001 From: James Liu Date: Fri, 5 Sep 2025 07:54:24 +0000 Subject: [PATCH 04/26] Fix typo. Co-authored-by: Alice Cecile --- crates/bevy_ptr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 7ddb5aeeb7164..dee980dff0dad 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -303,7 +303,7 @@ pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a m /// the memory pointed to by this pointer as it may be pointing to an element in a `Vec` or /// to a local in a function etc. /// -/// This type tries to act "borrow-like" like which means that: +/// This type tries to act "borrow-like" which means that: /// - Pointer should be considered exclusive and mutable. It cannot be cloned as this would lead /// to aliased mutability and potentially use after free bugs. /// - It must always points to a valid value of whatever the pointee type is. From d8add27d5f7fadbf42fd4858774d9ce1ff5d16e6 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 01:00:59 -0700 Subject: [PATCH 05/26] Add missing safety comment --- crates/bevy_ptr/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index dee980dff0dad..d482c2c318bf6 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -644,6 +644,12 @@ impl Debug for MovingPtr<'_, T, Unaligned> { impl<'a, T, A: IsAligned> From> for OwningPtr<'a, A> { #[inline] fn from(value: MovingPtr<'a, T, A>) -> Self { + // SAFETY: + // - `value.0` must always point to valid value of type `T`. + // - The type parameter `A` is mirrored from input to output, keeping the same alignment guarantees. + // - `value.0` by construction must have correct provenance to allow read and writes of type `T`. + // - The lifetime `'a` is mirrored from input to output, keeping the same lifetime guarantees. + // - `OwningPtr` maintains the same aliasing invariants as `MovingPtr`. let ptr = unsafe { OwningPtr::new(value.0.cast::()) }; mem::forget(value); ptr From a9d733337025b57bf48b1051d0c31f90b75c62f7 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 01:07:18 -0700 Subject: [PATCH 06/26] Update alignment comments --- crates/bevy_ptr/src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index d482c2c318bf6..233a0723121ab 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -254,7 +254,7 @@ impl<'a, T: ?Sized> From<&'a mut T> for ConstNonNull { /// - It should be considered immutable: its target must not be changed while this pointer is alive. /// - It must always points to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. -/// - Must be sufficiently aligned for the unknown pointee type. +/// - If `A` is [`Aligned`], the pointer must always be properly aligned for the unknown pointee type. /// /// It may be helpful to think of this type as similar to `&'a dyn Any` but without /// the metadata and able to point to data that does not correspond to a Rust type. @@ -269,7 +269,7 @@ pub struct Ptr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a u8, A)> /// aliased mutability. /// - It must always points to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. -/// - Must be sufficiently aligned for the unknown pointee type. +/// - If `A` is [`Aligned`], the pointer must always be properly aligned for the unknown pointee type. /// /// It may be helpful to think of this type as similar to `&'a mut dyn Any` but without /// the metadata and able to point to data that does not correspond to a Rust type. @@ -288,7 +288,7 @@ pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut /// to aliased mutability and potentially use after free bugs. /// - It must always points to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. -/// - Must be sufficiently aligned for the unknown pointee type. +/// - If `A` is [`Aligned`], the pointer must always be properly aligned for the unknown pointee type. /// /// It may be helpful to think of this type as similar to `&'a mut ManuallyDrop` but /// without the metadata and able to point to data that does not correspond to a Rust type. @@ -303,16 +303,13 @@ pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a m /// the memory pointed to by this pointer as it may be pointing to an element in a `Vec` or /// to a local in a function etc. /// -/// This type tries to act "borrow-like" which means that: +/// This type tries to act "borrow-like" like which means that: /// - Pointer should be considered exclusive and mutable. It cannot be cloned as this would lead /// to aliased mutability and potentially use after free bugs. /// - It must always points to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. /// - It does not support pointer arithmetic in any way. -/// - Must be sufficiently aligned for the pointee type. -/// -/// If the generic type parameter `A` is [`Aligned`] (the default), then all instances of the type -/// must be properly aligned for type `T`. +/// - If `A` is [`Aligned`], the pointer must always be properly aligned for the type `T`. #[repr(transparent)] pub struct MovingPtr<'a, T, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut T, A)>); From 40531b3b491a488eae96efd46a0f62a65506c67c Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 01:16:11 -0700 Subject: [PATCH 07/26] Provide links to alignment docs --- crates/bevy_ptr/src/lib.rs | 71 ++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 233a0723121ab..ef2e51d3750a2 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -16,11 +16,15 @@ use core::{ ptr::{self, NonNull}, }; -/// Used as a type argument to [`Ptr`], [`PtrMut`] and [`OwningPtr`] to specify that the pointer is aligned. +/// Used as a type argument to [`Ptr`], [`PtrMut`] and [`OwningPtr`] to specify that the pointer is [aligned]. +/// +/// [aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[derive(Debug, Copy, Clone)] pub struct Aligned; -/// Used as a type argument to [`Ptr`], [`PtrMut`] and [`OwningPtr`] to specify that the pointer is not aligned. +/// Used as a type argument to [`Ptr`], [`PtrMut`] and [`OwningPtr`] to specify that the pointer is not [aligned]. +/// +/// [aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[derive(Debug, Copy, Clone)] pub struct Unaligned; @@ -32,7 +36,9 @@ pub trait IsAligned: sealed::Sealed { /// # Safety /// - `ptr` must be valid for reads. /// - `ptr` must point to a valid instance of type `T` - /// - If this type is [`Aligned`], then `ptr` must be properly aligned for type `T`. + /// - If this type is [`Aligned`], then `ptr` must be [properly aligned] for type `T`. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[doc(hidden)] unsafe fn read_ptr(ptr: *const T) -> T; @@ -44,10 +50,12 @@ pub trait IsAligned: sealed::Sealed { /// # Safety /// - `ptr` must be valid for reads and writes. /// - `ptr` must point to a valid instance of type `T` - /// - If this type is [`Aligned`], then `ptr` must be properly aligned for type `T`. + /// - If this type is [`Aligned`], then `ptr` must be [properly aligned] for type `T`. /// - The value pointed to by `ptr` must be valid for dropping. /// - While `drop_in_place` is executing, the only way to access parts of `ptr` is through /// the `&mut Self` supplied to it's `Drop::drop` impl. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[doc(hidden)] unsafe fn drop_in_place(ptr: *mut T); } @@ -194,7 +202,7 @@ impl ConstNonNull { /// /// When calling this method, you have to ensure that all of the following is true: /// - /// * The pointer must be properly aligned. + /// * The pointer must be [properly aligned]. /// /// * It must be "dereferenceable" in the sense defined in [the module documentation]. /// @@ -222,6 +230,7 @@ impl ConstNonNull { /// ``` /// /// [the module documentation]: core::ptr#safety + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn as_ref<'a>(&self) -> &'a T { // SAFETY: This function's safety invariants are identical to `NonNull::as_ref` @@ -254,10 +263,12 @@ impl<'a, T: ?Sized> From<&'a mut T> for ConstNonNull { /// - It should be considered immutable: its target must not be changed while this pointer is alive. /// - It must always points to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. -/// - If `A` is [`Aligned`], the pointer must always be properly aligned for the unknown pointee type. +/// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the unknown pointee type. /// /// It may be helpful to think of this type as similar to `&'a dyn Any` but without /// the metadata and able to point to data that does not correspond to a Rust type. +/// +/// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[derive(Copy, Clone)] #[repr(transparent)] pub struct Ptr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a u8, A)>); @@ -269,10 +280,12 @@ pub struct Ptr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a u8, A)> /// aliased mutability. /// - It must always points to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. -/// - If `A` is [`Aligned`], the pointer must always be properly aligned for the unknown pointee type. +/// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the unknown pointee type. /// /// It may be helpful to think of this type as similar to `&'a mut dyn Any` but without /// the metadata and able to point to data that does not correspond to a Rust type. +/// +/// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[repr(transparent)] pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut u8, A)>); @@ -288,10 +301,12 @@ pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut /// to aliased mutability and potentially use after free bugs. /// - It must always points to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. -/// - If `A` is [`Aligned`], the pointer must always be properly aligned for the unknown pointee type. +/// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the unknown pointee type. /// /// It may be helpful to think of this type as similar to `&'a mut ManuallyDrop` but /// without the metadata and able to point to data that does not correspond to a Rust type. +/// +/// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[repr(transparent)] pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut u8, A)>); @@ -309,7 +324,9 @@ pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a m /// - It must always points to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. /// - It does not support pointer arithmetic in any way. -/// - If `A` is [`Aligned`], the pointer must always be properly aligned for the type `T`. +/// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the type `T`. +/// +/// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[repr(transparent)] pub struct MovingPtr<'a, T, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut T, A)>); @@ -430,10 +447,12 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { /// /// # Safety /// - `inner` must point to valid value of `T`. - /// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for `T`. + /// - If the `A` type parameter is [`Aligned`] then `inner` must be be [properly aligned] for `T`. /// - `inner` must have correct provenance to allow read and writes of the pointee type. /// - The lifetime `'a` must be constrained such that this [`MovingPtr`] will stay valid and nothing /// else can read or mutate the pointee while this [`MovingPtr`] is live. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn new(inner: NonNull) -> Self { Self(inner, PhantomData) @@ -522,7 +541,9 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { /// /// # Safety /// - `dst` must be valid for writes. - /// - If the `A` type parameter is [`Aligned`] then `dst` must be properly aligned for `T`. + /// - If the `A` type parameter is [`Aligned`] then `dst` must be [properly aligned] for `T`. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn write_to(self, dst: *mut T) { let src = self.0.as_ptr(); @@ -684,10 +705,12 @@ impl<'a, A: IsAligned> Ptr<'a, A> { /// /// # Safety /// - `inner` must point to valid value of whatever the pointee type is. - /// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for the pointee type. + /// - If the `A` type parameter is [`Aligned`] then `inner` must be be [properly aligned]for the pointee type. /// - `inner` must have correct provenance to allow reads of the pointee type. /// - The lifetime `'a` must be constrained such that this [`Ptr`] will stay valid and nothing /// can mutate the pointee while this [`Ptr`] is live except through an [`UnsafeCell`]. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn new(inner: NonNull) -> Self { Self(inner, PhantomData) @@ -708,8 +731,10 @@ impl<'a, A: IsAligned> Ptr<'a, A> { /// /// # Safety /// - `T` must be the erased pointee type for this [`Ptr`]. - /// - If the type parameter `A` is [`Unaligned`] then this pointer must be sufficiently aligned + /// - If the type parameter `A` is [`Unaligned`] then this pointer must be be [properly aligned] /// for the pointee type `T`. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn deref(self) -> &'a T { let ptr = self.as_ptr().cast::().debug_ensure_aligned(); @@ -741,10 +766,12 @@ impl<'a, A: IsAligned> PtrMut<'a, A> { /// /// # Safety /// - `inner` must point to valid value of whatever the pointee type is. - /// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for the pointee type. + /// - If the `A` type parameter is [`Aligned`] then `inner` must be be [properly aligned] for the pointee type. /// - `inner` must have correct provenance to allow read and writes of the pointee type. /// - The lifetime `'a` must be constrained such that this [`PtrMut`] will stay valid and nothing /// else can read or mutate the pointee while this [`PtrMut`] is live. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn new(inner: NonNull) -> Self { Self(inner, PhantomData) @@ -763,8 +790,10 @@ impl<'a, A: IsAligned> PtrMut<'a, A> { /// /// # Safety /// - `T` must be the erased pointee type for this [`PtrMut`]. - /// - If the type parameter `A` is [`Unaligned`] then this pointer must be sufficiently aligned + /// - If the type parameter `A` is [`Unaligned`] then this pointer must be be [properly aligned] /// for the pointee type `T`. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn deref_mut(self) -> &'a mut T { let ptr = self.as_ptr().cast::().debug_ensure_aligned(); @@ -832,10 +861,12 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> { /// /// # Safety /// - `inner` must point to valid value of whatever the pointee type is. - /// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for the pointee type. + /// - If the `A` type parameter is [`Aligned`] then `inner` must be [properly aligned] for the pointee type. /// - `inner` must have correct provenance to allow read and writes of the pointee type. /// - The lifetime `'a` must be constrained such that this [`OwningPtr`] will stay valid and nothing /// else can read or mutate the pointee while this [`OwningPtr`] is live. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn new(inner: NonNull) -> Self { Self(inner, PhantomData) @@ -845,8 +876,10 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> { /// /// # Safety /// - `T` must be the erased pointee type for this [`OwningPtr`]. - /// - If the type parameter `A` is [`Unaligned`] then this pointer must be sufficiently aligned + /// - If the type parameter `A` is [`Unaligned`] then this pointer must be be [properly aligned] /// for the pointee type `T`. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn read(self) -> T { let ptr = self.as_ptr().cast::().debug_ensure_aligned(); @@ -858,8 +891,10 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> { /// /// # Safety /// - `T` must be the erased pointee type for this [`OwningPtr`]. - /// - If the type parameter `A` is [`Unaligned`] then this pointer must be sufficiently aligned + /// - If the type parameter `A` is [`Unaligned`] then this pointer must be be [properly aligned] /// for the pointee type `T`. + /// + /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[inline] pub unsafe fn drop_as(self) { let ptr = self.as_ptr().cast::().debug_ensure_aligned(); From c5418ce4088c337313e7cf9158ca2c89acc906ed Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 01:30:40 -0700 Subject: [PATCH 08/26] Add MovingPtr to the README --- crates/bevy_ptr/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_ptr/README.md b/crates/bevy_ptr/README.md index cce9b80f29660..09a9f9061cb2b 100644 --- a/crates/bevy_ptr/README.md +++ b/crates/bevy_ptr/README.md @@ -96,6 +96,7 @@ multiple instances to collectively own the data it points to, and as a result, f |---------------------|-----------|-------|--------------|-------|--------|----------------|------------------| |`ConstNonNull` |No |No |Yes |No |Yes |No |Yes | |`ThinSlicePtr<'a, T>`|Yes |No |Yes |Yes |Yes |Yes |Yes | +|`MovingPtr<'a, T>` |Yes |Yes |Yes |Maybe |Yes |Yes |Yes | |`OwningPtr<'a>` |Yes |Yes |No |Maybe |Yes |Yes |No | |`Ptr<'a>` |Yes |No |No |Maybe |Yes |No |No | |`PtrMut<'a>` |Yes |Yes |No |Maybe |Yes |Yes |No | @@ -109,3 +110,8 @@ accessing elements in the slice is `unsafe`. In debug builds, the length is incl `OwningPtr<'a>`, `Ptr<'a>`, and `PtrMut<'a>` act like `NonNull<()>`, but attempts to restore much of the safety guarantees of `Unique`, `&T`, and `&mut T`. They allow working with heterogenous type erased storage (i.e. ECS tables, typemaps) without the overhead of dynamic dispatch in a manner that progressively translates back to safe borrows. These types also support optional alignment requirements at a type level, and will verify it on dereference in debug builds. + +`MovingPtr<'a, T>` is like a lifetimed-`Box` or a typed `OwningPtr<'a>` made for cheaply moving potentially large values around in memory. +It's a pointer that owns the value it points to but does not own the allocation. If dropped, it will drop the value it points to if it's dropped, just as +if you dropped a value of the inner type but won't deallocate the allocation where the value lived in. It provides a number of methods for moving the value +into another location in memory, including options for partial or deconstructive moves. \ No newline at end of file From 6c908a4db096661c9a59bd6668c2ec93b8c67b24 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 01:35:45 -0700 Subject: [PATCH 09/26] Markdown formatting --- crates/bevy_ptr/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ptr/README.md b/crates/bevy_ptr/README.md index 09a9f9061cb2b..ca70b80b8f34d 100644 --- a/crates/bevy_ptr/README.md +++ b/crates/bevy_ptr/README.md @@ -112,6 +112,6 @@ They allow working with heterogenous type erased storage (i.e. ECS tables, typem translates back to safe borrows. These types also support optional alignment requirements at a type level, and will verify it on dereference in debug builds. `MovingPtr<'a, T>` is like a lifetimed-`Box` or a typed `OwningPtr<'a>` made for cheaply moving potentially large values around in memory. -It's a pointer that owns the value it points to but does not own the allocation. If dropped, it will drop the value it points to if it's dropped, just as +It's a pointer that owns the value it points to but does not own the allocation. If dropped, it will drop the value it points to if it's dropped, just as if you dropped a value of the inner type but won't deallocate the allocation where the value lived in. It provides a number of methods for moving the value -into another location in memory, including options for partial or deconstructive moves. \ No newline at end of file +into another location in memory, including options for partial or deconstructive moves. From 75e108219390d96ea83381c13f7cc38c21fff205 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 01:50:05 -0700 Subject: [PATCH 10/26] Add an option to create a MovingPtr from a &mut MaybeUninit --- crates/bevy_ptr/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index ef2e51d3750a2..8c1f93d3d71cc 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -9,6 +9,7 @@ use core::{ cell::UnsafeCell, + f128::consts::PHI, fmt::{self, Debug, Formatter, Pointer}, marker::PhantomData, mem::{self, ManuallyDrop, MaybeUninit}, @@ -429,6 +430,7 @@ impl<'a, T> MovingPtr<'a, T, Aligned> { /// This exists mostly to reduce compile times; /// code is only duplicated per type, rather than per function called. fn make_internal(temp: &mut ManuallyDrop) -> MovingPtr<'_, T> { + // SAFETY: ManuallyDrop has the same memory layout as T MovingPtr(NonNull::from(temp).cast::(), PhantomData) } @@ -440,6 +442,19 @@ impl<'a, T> MovingPtr<'a, T, Aligned> { // so it's safe to promote it to an owning pointer. f(Self::make_internal(&mut val)) } + + /// Creates a [`MovingPtr`] from a provided value of type `T`. + /// + /// # Safety + /// - `value` must store a properly initialized value of type `T`. + /// - Once the returned [`MovingPtr`] has been used, `value` must be treated as + /// it were uninitialized unless it was explicitly leaked via [`core::mem::forget`]. + pub unsafe fn from_value(value: &'a mut MaybeUninit) -> Self { + // SAFETY: + // - MaybeUninit has the same memory layout as T + // - The caller guarantees that `value` must point to a valid instance of type `T`. + MovingPtr(NonNull::from(value).cast::(), PhantomData) + } } impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { From 3a609bead0b15cf0e0dba978fc32d2ef3bad46a6 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 01:52:45 -0700 Subject: [PATCH 11/26] Fix docs links --- crates/bevy_ptr/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 8c1f93d3d71cc..017e2348859ae 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -328,6 +328,7 @@ pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a m /// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the type `T`. /// /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment +/// [`Box`]: alloc::boxed::Box #[repr(transparent)] pub struct MovingPtr<'a, T, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut T, A)>); @@ -527,6 +528,8 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { /// insert(unsafe { field_c.assume_init() }); /// }); /// ``` + /// + /// [`forget`]: core::mem::forget #[inline] pub unsafe fn partial_move( ptr: MovingPtr<'a, T, A>, @@ -626,6 +629,9 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { /// insert(field_c); /// }); /// ``` + /// + /// [`forget`]: core::mem::forget + /// [`move_field`]: Self::move_field #[inline] pub unsafe fn move_field(&self, byte_offset: usize) -> MovingPtr<'a, U, Unaligned> { MovingPtr( From d88a9bfc886424a0c2e378f7f7953fc297faac61 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 01:53:21 -0700 Subject: [PATCH 12/26] Cleanup --- crates/bevy_ptr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 017e2348859ae..57f3078c2ff58 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -9,7 +9,6 @@ use core::{ cell::UnsafeCell, - f128::consts::PHI, fmt::{self, Debug, Formatter, Pointer}, marker::PhantomData, mem::{self, ManuallyDrop, MaybeUninit}, @@ -450,6 +449,7 @@ impl<'a, T> MovingPtr<'a, T, Aligned> { /// - `value` must store a properly initialized value of type `T`. /// - Once the returned [`MovingPtr`] has been used, `value` must be treated as /// it were uninitialized unless it was explicitly leaked via [`core::mem::forget`]. + #[inline] pub unsafe fn from_value(value: &'a mut MaybeUninit) -> Self { // SAFETY: // - MaybeUninit has the same memory layout as T From 8b16166a05726b1a364dd415703ab015975fd291 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 02:12:19 -0700 Subject: [PATCH 13/26] Docs fix --- crates/bevy_ptr/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 57f3078c2ff58..937213b6becae 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -16,13 +16,14 @@ use core::{ ptr::{self, NonNull}, }; -/// Used as a type argument to [`Ptr`], [`PtrMut`] and [`OwningPtr`] to specify that the pointer is [aligned]. +/// Used as a type argument to [`Ptr`], [`PtrMut`], [`OwningPtr`], and [`MovingPtr`] to specify that the pointer is guaranteed +/// to be [aligned]. /// /// [aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[derive(Debug, Copy, Clone)] pub struct Aligned; -/// Used as a type argument to [`Ptr`], [`PtrMut`] and [`OwningPtr`] to specify that the pointer is not [aligned]. +/// Used as a type argument to [`Ptr`], [`PtrMut`], [`OwningPtr`], and [`MovingPtr`] to specify that the pointer may not [aligned]. /// /// [aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment #[derive(Debug, Copy, Clone)] @@ -289,7 +290,7 @@ pub struct Ptr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a u8, A)> #[repr(transparent)] pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut u8, A)>); -/// Type-erased Box-like pointer to some unknown type chosen when constructing this type. +/// Type-erased [`Box`]-like pointer to some unknown type chosen when constructing this type. /// /// Conceptually represents ownership of whatever data is being pointed to and so is /// responsible for calling its `Drop` impl. This pointer is _not_ responsible for freeing @@ -307,6 +308,7 @@ pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut /// without the metadata and able to point to data that does not correspond to a Rust type. /// /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment +/// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html #[repr(transparent)] pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut u8, A)>); @@ -327,7 +329,7 @@ pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a m /// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the type `T`. /// /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment -/// [`Box`]: alloc::boxed::Box +/// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html #[repr(transparent)] pub struct MovingPtr<'a, T, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut T, A)>); From e39bc5f7c2adbbb0f67bca77b41f4891b3f47425 Mon Sep 17 00:00:00 2001 From: James Liu Date: Fri, 5 Sep 2025 17:53:48 +0000 Subject: [PATCH 14/26] Typo Co-authored-by: Giacomo Stevanato --- crates/bevy_ptr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 937213b6becae..7caadb763befd 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -587,7 +587,7 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { /// /// # Safety /// - `U` must be the correct type for the field at `byte_offset` within `self`. - /// - `self` should not be accesesed or dropped as if it were a complete value. + /// - `self` should not be accessed or dropped as if it were a complete value. /// Other fields that have not been moved out of may still be accessed or dropped separately. /// - This function cannot alias the field with any other access, including other calls to [`move_field`] /// for the same field, without first calling [`forget`] on it first. From a350b2d111e602cfa84881fa78a0b8e3d953e2a7 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 10:55:38 -0700 Subject: [PATCH 15/26] Avoid leaking in TryFrom impl --- crates/bevy_ptr/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 7caadb763befd..d84a2e3e5bd7c 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -698,15 +698,14 @@ impl<'a, T, A: IsAligned> From> for OwningPtr<'a, A> { } impl<'a, T> TryFrom> for MovingPtr<'a, T, Aligned> { - type Error = Unaligned; + type Error = MovingPtr<'a, T, Unaligned>; #[inline] fn try_from(value: MovingPtr<'a, T, Unaligned>) -> Result { let ptr = value.0; - mem::forget(value); if ptr.as_ptr().is_aligned() { Ok(MovingPtr(ptr, PhantomData)) } else { - Err(Unaligned) + Err(value) } } } From a4466414e1cbc4a0027b1d2c322033491b8bb2e7 Mon Sep 17 00:00:00 2001 From: James Liu Date: Fri, 5 Sep 2025 19:41:10 +0000 Subject: [PATCH 16/26] typo Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> --- crates/bevy_ptr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index d84a2e3e5bd7c..0168b5fdcc3ef 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -578,7 +578,7 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { /// Creates a [`MovingPtr`] for a specific field within `self`. /// - /// This function is expliciitly made for deconstructive moves. + /// This function is explicitly made for deconstructive moves. /// /// The correct `byte_offset` for a field can be obtained via [`core::mem::offset_of`]. /// From 52904d7bf662e0ac1abdf68b69db1ed680dcf22d Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 12:41:33 -0700 Subject: [PATCH 17/26] Add a conversion from OwningPtr to MovingPtr --- crates/bevy_ptr/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 0168b5fdcc3ef..3c0730d0be1f7 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -909,6 +909,15 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> { unsafe { ptr.read() } } + /// Casts to a concrete type as a [`MovingPtr`]. + /// + /// # Safety + /// - `T` must be the erased pointee type for this [`OwningPtr`]. + #[inline] + pub unsafe fn cast(self) -> MovingPtr<'a, T, A> { + MovingPtr(self.0.cast::(), PhantomData) + } + /// Consumes the [`OwningPtr`] to drop the underlying data of type `T`. /// /// # Safety From d0a9edd9feae53eccbfe4f490025603e2721b801 Mon Sep 17 00:00:00 2001 From: James Liu Date: Fri, 5 Sep 2025 19:47:11 +0000 Subject: [PATCH 18/26] drop_in_place docs Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> --- crates/bevy_ptr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 3c0730d0be1f7..13d37a597b4b4 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -46,7 +46,7 @@ pub trait IsAligned: sealed::Sealed { #[doc(hidden)] unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, size: usize); - /// Reads the value pointed to by `ptr`. + /// Executes the destructor (if any) of the value pointed to by `ptr`. /// /// # Safety /// - `ptr` must be valid for reads and writes. From 833a8a96d8b4d1aca0bd4c9e130d31b509ac3dd2 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 15:17:31 -0700 Subject: [PATCH 19/26] Add a safe assign_to alternative to write_to --- crates/bevy_ptr/src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 13d37a597b4b4..ca4f8027f8ec1 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -576,6 +576,26 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { unsafe { A::copy_nonoverlapping(src, dst, 1) }; } + /// Writes the value pointed to by this pointer into `dst`. + /// + /// The value previously stored at `dst` will be dropped. + #[inline] + pub fn assign_to(self, dst: &mut T) { + // SAFETY: + // - `dst` is a mutable borrow, it must point to a valid instance of `T`. + // - `dst` is a mutable borrow, it must point to value that is valid for dropping. + // - `dst` is a mutable borrow, it must not alias any other access. + unsafe { + ptr::drop_in_place(dst); + } + // SAFETY: + // - `dst` is a mutable borrow, it must be valid for writes. + // - `dst` is a mutable borrow, it must always be aligned. + unsafe { + self.write_to(dst); + } + } + /// Creates a [`MovingPtr`] for a specific field within `self`. /// /// This function is explicitly made for deconstructive moves. From a300c72c4e9a3ddcdc13d4bdbe3c020a9e8edf11 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 19:06:15 -0700 Subject: [PATCH 20/26] Add deconstruct_moving_ptr --- crates/bevy_ptr/src/lib.rs | 150 +++++++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index ca4f8027f8ec1..73cab8e1bc91e 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -43,10 +43,21 @@ pub trait IsAligned: sealed::Sealed { #[doc(hidden)] unsafe fn read_ptr(ptr: *const T) -> T; + /// Copies `count * size_of::()` bytes from `src` to `dst`. The source + /// and destination must *not* overlap. + /// + /// # Safety + /// - `src` must be valid for reads of `count * size_of::()` bytes. + /// - `dst` must be valid for writes of `count * size_of::()` bytes. + /// - The region of memory beginning at `src` with a size of `count * + /// size_of::()` bytes must *not* overlap with the region of memory + /// beginning at `dst` with the same size. + /// - If this type is [`Aligned`], then both `src` and `dst` must properly + /// be aligned for values of type `T`. #[doc(hidden)] unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, size: usize); - /// Executes the destructor (if any) of the value pointed to by `ptr`. + /// Reads the value pointed to by `ptr`. /// /// # Safety /// - `ptr` must be valid for reads and writes. @@ -262,7 +273,7 @@ impl<'a, T: ?Sized> From<&'a mut T> for ConstNonNull { /// /// This type tries to act "borrow-like" which means that: /// - It should be considered immutable: its target must not be changed while this pointer is alive. -/// - It must always points to a valid value of whatever the pointee type is. +/// - It must always point to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. /// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the unknown pointee type. /// @@ -279,7 +290,7 @@ pub struct Ptr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a u8, A)> /// This type tries to act "borrow-like" which means that: /// - Pointer is considered exclusive and mutable. It cannot be cloned as this would lead to /// aliased mutability. -/// - It must always points to a valid value of whatever the pointee type is. +/// - It must always point to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. /// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the unknown pointee type. /// @@ -297,10 +308,10 @@ pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a mut /// the memory pointed to by this pointer as it may be pointing to an element in a `Vec` or /// to a local in a function etc. /// -/// This type tries to act "borrow-like" like which means that: +/// This type tries to act "borrow-like" which means that: /// - Pointer should be considered exclusive and mutable. It cannot be cloned as this would lead /// to aliased mutability and potentially use after free bugs. -/// - It must always points to a valid value of whatever the pointee type is. +/// - It must always point to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. /// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the unknown pointee type. /// @@ -320,14 +331,17 @@ pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull, PhantomData<(&'a m /// the memory pointed to by this pointer as it may be pointing to an element in a `Vec` or /// to a local in a function etc. /// -/// This type tries to act "borrow-like" like which means that: +/// This type tries to act "borrow-like" which means that: /// - Pointer should be considered exclusive and mutable. It cannot be cloned as this would lead /// to aliased mutability and potentially use after free bugs. -/// - It must always points to a valid value of whatever the pointee type is. +/// - It must always point to a valid value of whatever the pointee type is. /// - The lifetime `'a` accurately represents how long the pointer is valid for. /// - It does not support pointer arithmetic in any way. /// - If `A` is [`Aligned`], the pointer must always be [properly aligned] for the type `T`. /// +/// A value can be deconstructed into its fields via [`deconstruct_moving_ptr`], see it's documentation +/// for an example on how to use it. +/// /// [properly aligned]: https://doc.rust-lang.org/std/ptr/index.html#alignment /// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html #[repr(transparent)] @@ -421,17 +435,15 @@ impl_ptr!(Ptr); impl_ptr!(PtrMut); impl_ptr!(OwningPtr); -impl<'a, T> MovingPtr<'a, T, Aligned> { - /// Removes the alignment requirement of this pointer - pub fn to_unaligned(self) -> MovingPtr<'a, T, Unaligned> { - let value = MovingPtr(self.0, PhantomData); - mem::forget(self); - value - } - +impl MovingPtr<'_, T, Aligned> { /// This exists mostly to reduce compile times; /// code is only duplicated per type, rather than per function called. - fn make_internal(temp: &mut ManuallyDrop) -> MovingPtr<'_, T> { + /// + /// # Safety + /// - `value` must store a properly initialized value of type `T`. + /// - Once the returned [`MovingPtr`] has been used, `value` must be treated as + /// it were uninitialized unless it was explicitly leaked via [`core::mem::forget`]. + unsafe fn make_internal(temp: &mut MaybeUninit) -> MovingPtr<'_, T> { // SAFETY: ManuallyDrop has the same memory layout as T MovingPtr(NonNull::from(temp).cast::(), PhantomData) } @@ -439,10 +451,19 @@ impl<'a, T> MovingPtr<'a, T, Aligned> { /// Consumes a value and creates an [`MovingPtr`] to it while ensuring a double drop does not happen. #[inline] pub fn make) -> R, R>(val: T, f: F) -> R { - let mut val = ManuallyDrop::new(val); - // SAFETY: The value behind the pointer will not get dropped or observed later, - // so it's safe to promote it to an owning pointer. - f(Self::make_internal(&mut val)) + let mut val = MaybeUninit::new(val); + // SAFETY: The value behind the pointer will not get dropped or observed later. + f(unsafe { Self::make_internal(&mut val) }) + } +} + +impl<'a, T> MovingPtr<'a, T, Aligned> { + /// Removes the alignment requirement of this pointer + #[inline] + pub fn to_unaligned(self) -> MovingPtr<'a, T, Unaligned> { + let value = MovingPtr(self.0, PhantomData); + mem::forget(self); + value } /// Creates a [`MovingPtr`] from a provided value of type `T`. @@ -515,19 +536,16 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { /// // - It is impossible for the provided closure to drop the provided pointer as `move_field` cannot panic. /// // - `field_a` and `field_b` are moved out of but never accessed after this. /// let partial_parent = MovingPtr::partial_move(parent_ptr, |parent_ptr| { - /// let field_a = parent_ptr.move_field::(offset_of!(Parent, field_a)); - /// let field_b = parent_ptr.move_field::(offset_of!(Parent, field_b)); - /// // Each call to insert may panic! Ensure that `parent_ptr` cannot be dropped before - /// // calling them! - /// core::mem::forget(parent_ptr); - /// insert(field_a); - /// insert(field_b); + /// bevy_ptr::deconstruct_moving_ptr!(parent_ptr, Parent { + /// field_a: FieldAType => { insert(field_a) }, + /// field_b: FieldBType => { insert(field_b) }, + /// }); /// }); /// /// // Move the rest of fields out of the parent. - /// // SAFETY: The fields were not moved out of in the above `partial_move` and thus must still be valid. - /// let field_c = partial_parent.move_field::>(offset_of!(Parent, field_c)); - /// insert(unsafe { field_c.assume_init() }); + /// bevy_ptr::deconstruct_moving_ptr!(partial_parent, Parent { + /// field_c: FieldBType => { insert(field_c) }, + /// }); /// }); /// ``` /// @@ -596,7 +614,8 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { } } - /// Creates a [`MovingPtr`] for a specific field within `self`. + /// Creates a [`MovingPtr`] for a specific field within `self`. This is a building block for + /// [`deconstruct_moving_ptr`] and should generally not be accessed directly. /// /// This function is explicitly made for deconstructive moves. /// @@ -655,6 +674,7 @@ impl<'a, T, A: IsAligned> MovingPtr<'_, T, A> { /// [`forget`]: core::mem::forget /// [`move_field`]: Self::move_field #[inline] + #[doc(hidden)] pub unsafe fn move_field(&self, byte_offset: usize) -> MovingPtr<'a, U, Unaligned> { MovingPtr( // SAFETY: The caller must ensure that `U` is the correct type for the field at `byte_offset`. @@ -1132,3 +1152,71 @@ impl DebugEnsureAligned for *mut T { self } } + +/// Deconstructs a [`MovingPtr`] into its individual fields. +/// +/// This consumes the [`MovingPtr`] and hands out [`MovingPtr`] wrappers around +/// pointers to each of its fields. The value will *not* be dropped. +/// +/// The field move expressions will be executed in the order they're provided to the macro. +/// In the example below, the call to [`assign_to`] for `field_a` will always run before the +/// calls for `field_b` and `field_c`. +/// +/// # Safety +/// This macro generates unsafe code and must be set up correctly to avoid undefined behavior. +/// - The provided type must match the type of the value pointed to by the [`MovingPtr`]. +/// - The type and name of the fields must match the type's definition. For tuples and tuple structs, +/// this would be the tuple indicies. +/// +/// # Example +/// +/// ``` +/// use core::mem::{offset_of, MaybeUninit}; +/// use bevy_ptr::MovingPtr; +/// # use bevy_ptr::Unaligned; +/// # struct FieldAType(usize); +/// # struct FieldBType(usize); +/// # struct FieldCType(usize); +/// +/// pub struct Parent { +/// pub field_a: FieldAType, +/// pub field_b: FieldBType, +/// pub field_c: FieldCType, +/// } +/// +/// let parent = Parent { +/// field_a: FieldAType(11), +/// field_b: FieldBType(22), +/// field_c: FieldCType(33), +/// }; +/// +/// let mut target_a = FieldAType(101); +/// let mut target_b = FieldBType(102); +/// let mut target_c = FieldCType(103); +/// +/// MovingPtr::make(parent, |parent_ptr| unsafe { +/// bevy_ptr::deconstruct_moving_ptr!(parent_ptr, Parent { +/// // The field name and type must match the name used in the type definition. +/// // Each one will be a `MovingPtr` of the supplied type +/// field_a: FieldAType => { field_a.assign_to(&mut target_a) }, +/// field_b: FieldBType => { field_b.assign_to(&mut target_b) }, +/// field_c: FieldCType => { field_c.assign_to(&mut target_c) }, +/// }); +/// }); +/// +/// assert_eq!(target_a.0, 11); +/// assert_eq!(target_b.0, 22); +/// assert_eq!(target_c.0, 33); +/// ``` +#[macro_export] +macro_rules! deconstruct_moving_ptr { + ($ptr:ident, $self_type:tt {$($field_name:tt: $field_type:tt => $field_block:block,)*}) => { + unsafe { + $(let $field_name = $ptr.move_field::<$field_type>(core::mem::offset_of!($self_type, $field_name));)* + // Each field block may panic! Ensure that `parent_ptr` cannot be dropped before + // calling them! + core::mem::forget($ptr); + $($field_block)* + } + }; +} From 5cc6fc9780355f210eb2273a32a6df67852d7b44 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 19:10:00 -0700 Subject: [PATCH 21/26] Tuple --- crates/bevy_ptr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 73cab8e1bc91e..942bf7c8a9217 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -1166,7 +1166,7 @@ impl DebugEnsureAligned for *mut T { /// This macro generates unsafe code and must be set up correctly to avoid undefined behavior. /// - The provided type must match the type of the value pointed to by the [`MovingPtr`]. /// - The type and name of the fields must match the type's definition. For tuples and tuple structs, -/// this would be the tuple indicies. +/// this would be the tuple indices. /// /// # Example /// From 4e2a1c84fcfbd4ad9f9984d6eb4c90ea328f8217 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 19:17:00 -0700 Subject: [PATCH 22/26] Remove unnecesary unsafe block --- crates/bevy_ptr/src/lib.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 942bf7c8a9217..4ac59b2b210d9 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -1211,12 +1211,10 @@ impl DebugEnsureAligned for *mut T { #[macro_export] macro_rules! deconstruct_moving_ptr { ($ptr:ident, $self_type:tt {$($field_name:tt: $field_type:tt => $field_block:block,)*}) => { - unsafe { - $(let $field_name = $ptr.move_field::<$field_type>(core::mem::offset_of!($self_type, $field_name));)* - // Each field block may panic! Ensure that `parent_ptr` cannot be dropped before - // calling them! - core::mem::forget($ptr); - $($field_block)* - } + $(let $field_name = $ptr.move_field::<$field_type>(core::mem::offset_of!($self_type, $field_name));)* + // Each field block may panic! Ensure that `parent_ptr` cannot be dropped before + // calling them! + core::mem::forget($ptr); + $($field_block)* }; } From ba12eaf6bc98c7b1a57ae0106aa9266e18853886 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 5 Sep 2025 19:56:38 -0700 Subject: [PATCH 23/26] Fix doc links --- crates/bevy_ptr/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 4ac59b2b210d9..e8052fdb5e193 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -1208,6 +1208,8 @@ impl DebugEnsureAligned for *mut T { /// assert_eq!(target_b.0, 22); /// assert_eq!(target_c.0, 33); /// ``` +/// +/// [`assign_to`]: MovingPtr::assign_to #[macro_export] macro_rules! deconstruct_moving_ptr { ($ptr:ident, $self_type:tt {$($field_name:tt: $field_type:tt => $field_block:block,)*}) => { From 6206fb4f2bda02e674244cbb929b3b7d7c7a7d31 Mon Sep 17 00:00:00 2001 From: James Liu Date: Sat, 6 Sep 2025 21:09:11 +0000 Subject: [PATCH 24/26] Don't repeat Co-authored-by: Sandor --- crates/bevy_ptr/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ptr/README.md b/crates/bevy_ptr/README.md index ca70b80b8f34d..864116673982d 100644 --- a/crates/bevy_ptr/README.md +++ b/crates/bevy_ptr/README.md @@ -112,6 +112,6 @@ They allow working with heterogenous type erased storage (i.e. ECS tables, typem translates back to safe borrows. These types also support optional alignment requirements at a type level, and will verify it on dereference in debug builds. `MovingPtr<'a, T>` is like a lifetimed-`Box` or a typed `OwningPtr<'a>` made for cheaply moving potentially large values around in memory. -It's a pointer that owns the value it points to but does not own the allocation. If dropped, it will drop the value it points to if it's dropped, just as +It's a pointer that owns the value it points to but does not own the allocation. If dropped, it will drop the value it points to, just as if you dropped a value of the inner type but won't deallocate the allocation where the value lived in. It provides a number of methods for moving the value into another location in memory, including options for partial or deconstructive moves. From 12f9094fb8a9644e4eb55418558080f0df0bbf99 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 7 Sep 2025 00:38:02 -0700 Subject: [PATCH 25/26] Replace make_internal with from_value --- crates/bevy_ptr/src/lib.rs | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index e8052fdb5e193..6790b0c0c309d 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -435,28 +435,6 @@ impl_ptr!(Ptr); impl_ptr!(PtrMut); impl_ptr!(OwningPtr); -impl MovingPtr<'_, T, Aligned> { - /// This exists mostly to reduce compile times; - /// code is only duplicated per type, rather than per function called. - /// - /// # Safety - /// - `value` must store a properly initialized value of type `T`. - /// - Once the returned [`MovingPtr`] has been used, `value` must be treated as - /// it were uninitialized unless it was explicitly leaked via [`core::mem::forget`]. - unsafe fn make_internal(temp: &mut MaybeUninit) -> MovingPtr<'_, T> { - // SAFETY: ManuallyDrop has the same memory layout as T - MovingPtr(NonNull::from(temp).cast::(), PhantomData) - } - - /// Consumes a value and creates an [`MovingPtr`] to it while ensuring a double drop does not happen. - #[inline] - pub fn make) -> R, R>(val: T, f: F) -> R { - let mut val = MaybeUninit::new(val); - // SAFETY: The value behind the pointer will not get dropped or observed later. - f(unsafe { Self::make_internal(&mut val) }) - } -} - impl<'a, T> MovingPtr<'a, T, Aligned> { /// Removes the alignment requirement of this pointer #[inline] @@ -466,6 +444,14 @@ impl<'a, T> MovingPtr<'a, T, Aligned> { value } + /// Consumes a value and creates an [`MovingPtr`] to it while ensuring a double drop does not happen. + #[inline] + pub fn make) -> R, R>(val: T, f: F) -> R { + let mut val = MaybeUninit::new(val); + // SAFETY: The value behind the pointer will not get dropped or observed later. + f(unsafe { MovingPtr::from_value(&mut val) }) + } + /// Creates a [`MovingPtr`] from a provided value of type `T`. /// /// # Safety From 6bb2fa3d6fe38414a134ee40cfa607fc3818adca Mon Sep 17 00:00:00 2001 From: james7132 Date: Sun, 7 Sep 2025 13:05:33 -0700 Subject: [PATCH 26/26] size -> count --- crates/bevy_ptr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index 6790b0c0c309d..ea5f741e4391f 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -55,7 +55,7 @@ pub trait IsAligned: sealed::Sealed { /// - If this type is [`Aligned`], then both `src` and `dst` must properly /// be aligned for values of type `T`. #[doc(hidden)] - unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, size: usize); + unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); /// Reads the value pointed to by `ptr`. ///