From cf01541515a0a596bf8bb486bd80484531d28014 Mon Sep 17 00:00:00 2001 From: soruh Date: Sun, 26 Jul 2026 14:03:24 +0200 Subject: [PATCH] start refactor (broken) --- src/lib.rs | 185 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 153 insertions(+), 32 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7b88429..74b99ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,23 +8,78 @@ use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ptr::NonNull; -/// Stores the capacity of a `Vec` for later reuse as a `Vec` where `V` shares the same `Layout` as `U`. -pub struct TypeErasedVec { +#[derive(Clone, Copy)] +struct VecParts { /// Pointer to the underlying allocation ptr: NonNull, /// Number of elements in the underlying vec len: usize, /// Size of the underlying allocation cap: usize, +} + +struct TypeErasedVecVtable { + /// Function pointer to reserve additional capacity in the underlying Vec + reserve_vector: unsafe fn(VecParts, usize) -> (VecParts, usize), + /// Function pointer to clear the underlying capacity of its old elements + clear_vector: unsafe fn(VecParts) -> VecParts, + /// Function pointer to the original type's drop logic. + drop_vector: unsafe fn(VecParts), +} + +impl TypeErasedVecVtable { + fn new() -> Self { + unsafe fn drop_vec(ptr: NonNull, len: usize, cap: usize) { + // SAFETY: We reconstruct the Vec to let its Drop impl handle deallocation. + _ = unsafe { Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap) }; + } + + unsafe fn clear_vec(ptr: NonNull, len: usize, cap: usize) { + // SAFETY: We reconstruct the Vec to call clear, dropping only the elements. + // Using into_raw_parts ensures we don't accidentally drop the allocation itself. + unsafe { + let mut vec = Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap); + vec.clear(); + let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec); + debug_assert!(std::ptr::eq(new_ptr, ptr.as_ptr().cast())); + debug_assert_eq!(new_cap, cap); + debug_assert_eq!(new_len, 0); + } + } + + unsafe fn reserve_vec( + ptr: NonNull, + len: usize, + cap: usize, + additional: usize, + ) -> (NonNull, usize) { + // SAFETY: We reconstruct the Vec to trigger a reserve. + // Capacity might change upon reallocation. + unsafe { + let mut vec = Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap); + vec.reserve(additional); + let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec); + debug_assert_eq!(new_len, len); + (NonNull::new_unchecked(new_ptr).cast::(), new_cap) + } + } + + Self { + reserve_vector: reserve_vec::, + clear_vector: clear_vec::, + drop_vector: drop_vec::, + } + } +} + +/// Stores the capacity of a `Vec` for later reuse as a `Vec` where `V` shares the same `Layout` as `U`. +pub struct TypeErasedVec { + parts: VecParts, /// The Layout the capacity was allocated with. We need this to confirm that /// any future conversion back to a `Vec` use the correct `Layout`. layout: Layout, - /// Function pointer to reserve additional capacity in the underlying Vec - reserve_vector: unsafe fn(NonNull, usize, usize, usize) -> (NonNull, usize), - /// Function pointer to clear the underlying capacity of its old elements - clear_vector: unsafe fn(NonNull, usize, usize), - /// Function pointer to the original type's drop logic. - drop_vector: unsafe fn(NonNull, usize, usize), + + vtable: TypeErasedVecVtable, } /// Provides access to a `TypeErasedVec` with a temporarily fixed type `T` @@ -37,12 +92,16 @@ impl<'vec, T> ContentGuard<'vec, T> { pub fn take(&mut self) -> Vec { let erased = std::mem::replace(self.erased, TypeErasedVec::new(Vec::::new())); let erased = ManuallyDrop::new(erased); + // SAFETY: The erased pointer, length, and capacity are valid for a Vec. + // ManuallyDrop prevents the old TypeErasedVec from double-freeing the allocation. unsafe { Vec::from_raw_parts(erased.ptr.as_ptr().cast::(), erased.len, erased.cap) } } pub fn with(&mut self, f: impl FnOnce(&mut Vec) -> R) -> R { let mut vec = self.take(); - // todo: is this panic / unwind safe? + // This is unwind-safe. If the closure panics, the inner `Vec` drops normally. + // The `erased` reference was swapped with a 0-capacity vector inside `take()`, + // preventing any double-free or memory leak. let res = f(&mut vec); *self.erased = TypeErasedVec::new(vec); res @@ -72,26 +131,30 @@ impl<'vec, T> ContentGuard<'vec, T> { } pub fn as_slice(&self) -> &[T] { + // SAFETY: The pointer and length correctly represent the currently initialized elements. unsafe { std::slice::from_raw_parts(self.erased.ptr.as_ptr().cast(), self.erased.len) } } pub fn as_slice_mut(&mut self) -> &mut [T] { + // SAFETY: The pointer and length correctly represent the currently initialized elements. unsafe { std::slice::from_raw_parts_mut(self.erased.ptr.as_ptr().cast(), self.erased.len) } } } impl TypeErasedVec { /// Type erase the underlying Capacity of a `Vec` remembering the `Layout` it was allocated with. - /// Any remaining elements in the `Vec` will be dropped. + /// Elements inside the `Vec` are retained. /// Conversion back to a `Vec` is only allowed for types with the same Layout. #[must_use] pub fn new(vec: Vec) -> Self { - // Define a drop function bound to the original type. unsafe fn drop_vec(ptr: NonNull, len: usize, cap: usize) { + // SAFETY: We reconstruct the Vec to let its Drop impl handle deallocation. _ = unsafe { Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap) }; } - // Define a clear function bound to the original type. + unsafe fn clear_vec(ptr: NonNull, len: usize, cap: usize) { + // SAFETY: We reconstruct the Vec to call clear, dropping only the elements. + // Using into_raw_parts ensures we don't accidentally drop the allocation itself. unsafe { let mut vec = Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap); vec.clear(); @@ -101,18 +164,19 @@ impl TypeErasedVec { debug_assert_eq!(new_len, 0); } } - // Define a reserve function bound to the original type. + unsafe fn reserve_vec( ptr: NonNull, len: usize, cap: usize, additional: usize, ) -> (NonNull, usize) { + // SAFETY: We reconstruct the Vec to trigger a reserve. + // Capacity might change upon reallocation. unsafe { let mut vec = Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap); vec.reserve(additional); let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec); - debug_assert_eq!(new_cap, cap); debug_assert_eq!(new_len, len); (NonNull::new_unchecked(new_ptr).cast::(), new_cap) } @@ -121,6 +185,7 @@ impl TypeErasedVec { let layout = Layout::new::(); let (ptr, len, cap) = vec.into_raw_parts(); + // SAFETY: Vec guarantees its underlying pointer is non-null. let ptr = unsafe { NonNull::new_unchecked(ptr.cast::()) }; Self { @@ -128,34 +193,30 @@ impl TypeErasedVec { cap, len, layout, - reserve_vector: reserve_vec::, - clear_vector: clear_vec::, - drop_vector: drop_vec::, + vtable: TypeErasedVecVtable::new::(), } } - /// Clear any remaining elements in the vector, dropping them - /// Calls `Vec::::clear` where `T` is the type the elements were intially stored with + /// Clear any remaining elements in the vector, dropping them. pub fn clear(&mut self) { + // SAFETY: The clear function correctly targets the currently stored type elements. unsafe { (self.clear_vector)(self.ptr, self.len, self.cap) }; self.len = 0; } - /// Reserve additional elements in the vector - /// Calls `Vec::::reserve` where `T` is the type the elements were intially stored with + /// Reserve additional elements in the vector. pub fn reserve(&mut self, additional: usize) { + // SAFETY: The reserve function correctly targets the currently stored type elements. unsafe { (self.ptr, self.cap) = (self.reserve_vector)(self.ptr, self.len, self.cap, additional); } } - /// Get the layout of the underlying capacity #[must_use] pub fn layout(&self) -> Layout { self.layout } - /// Get the underlying capacity in units of the `Layout` size #[must_use] pub fn capacity(&self) -> usize { self.cap @@ -171,7 +232,6 @@ impl TypeErasedVec { self.len == 0 } - /// Get the underlying capacity in bytes #[must_use] pub fn capacity_bytes(&self) -> usize { self.cap * self.layout.size() @@ -180,13 +240,49 @@ impl TypeErasedVec { /// Access the erased capacity with a temporarily fixed type. /// Casts the present elements to the new type without calling their destructor. /// - /// Safety: TODO: all required invariants for casting + drop change + /// # Safety + /// The caller must ensure that the existing elements can be safely transmuted + /// into the new type `T` and that dropping them as `T` is sound. pub unsafe fn try_cast_type<'vec, T>(&'vec mut self) -> Option> { if self.layout != Layout::new::() { return None; } - // todo: set function pointers + // Overwrite the function pointers to target the new type. + // This guarantees that any subsequent drop or clear invokes the correct destructors. + unsafe fn drop_vec(ptr: NonNull, len: usize, cap: usize) { + _ = unsafe { Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap) }; + } + + unsafe fn clear_vec(ptr: NonNull, len: usize, cap: usize) { + unsafe { + let mut vec = Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap); + vec.clear(); + let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec); + debug_assert!(std::ptr::eq(new_ptr, ptr.as_ptr().cast())); + debug_assert_eq!(new_len, 0); + debug_assert_eq!(new_cap, cap); + } + } + + unsafe fn reserve_vec( + ptr: NonNull, + len: usize, + cap: usize, + additional: usize, + ) -> (NonNull, usize) { + unsafe { + let mut vec = Vec::from_raw_parts(ptr.as_ptr().cast::(), len, cap); + vec.reserve(additional); + let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec); + debug_assert_eq!(new_len, len); + (NonNull::new_unchecked(new_ptr).cast::(), new_cap) + } + } + + self.drop_vector = drop_vec::; + self.clear_vector = clear_vec::; + self.reserve_vector = reserve_vec::; Some(ContentGuard { erased: self, @@ -195,10 +291,11 @@ impl TypeErasedVec { } /// Access the erased capacity with a temporarily fixed type. - /// Clears all elements currently stored in the `TypeErasedVector` - /// + /// Clears all elements currently stored in the `TypeErasedVec`. pub fn try_to_type<'vec, T>(&'vec mut self) -> Option> { self.clear(); + // SAFETY: The vector has been cleared, meaning there are no existing elements + // that could be invalidated or improperly dropped by the cast. unsafe { self.try_cast_type() } } @@ -214,26 +311,28 @@ impl TypeErasedVec { }) } + /// # Safety + /// `T` must perfectly match the `Layout` of the erased allocation. pub unsafe fn to_type_unchecked<'vec, T>(&'vec mut self) -> ContentGuard<'vec, T> { let layout = self.layout; let res = self.try_to_type(); if cfg!(debug_assertions) && res.is_none() { unreachable!( - "Calling `into_vec_unchecked` with an incompatible layout is UB! Target type layout must exactly match the erased layout. Capacity is reserved for {:?} but {} has {:?}", + "Calling `to_type_unchecked` with an incompatible layout is UB! Target type layout must exactly match the erased layout. Capacity is reserved for {:?} but {} has {:?}", layout, std::any::type_name::(), Layout::new::() ) } + // SAFETY: Ensured by the caller. unsafe { res.unwrap_unchecked() } } /// Convert the capacity of the erased `Vec` into a `Vec`. /// /// # Errors - /// if `T` does not have the same `Layout` as the underlying capacity - /// in this case the erased capacity will be returned in the `Err`. + /// Returns the original `TypeErasedVec` if `T` does not have the same `Layout`. pub fn try_into_vec(mut self) -> Result, Self> { if let Some(mut guard) = self.try_to_type::() { Ok(guard.take()) @@ -245,7 +344,7 @@ impl TypeErasedVec { /// Convert the capacity of the erased `Vec` into a `Vec`. /// /// # Panics - /// If `T` does not have the same `Layout` as the underlying capacity. + /// Panics if `T` does not have the same `Layout` as the underlying capacity. #[must_use] pub fn into_vec(mut self) -> Vec { self.to_type::().take() @@ -257,14 +356,36 @@ impl TypeErasedVec { /// `T` must have the same `Layout` as the underlying capacity. #[must_use] pub unsafe fn into_vec_unchecked(mut self) -> Vec { + // SAFETY: Ensured by the caller. unsafe { self.to_type_unchecked().take() } } + + /// Clear a type erased vec allowing it be safetly shared across threads, preserving the capacity + pub fn send(mut self) -> SendableTypeErasedVec { + self.clear(); + SendableTypeErasedVec(self) + } } impl Drop for TypeErasedVec { fn drop(&mut self) { + // SAFETY: The correct drop vector executes the destructors and frees the allocation. unsafe { (self.drop_vector)(self.ptr, self.len, self.cap); } } } + +pub struct SendableTypeErasedVec(TypeErasedVec); + +impl SendableTypeErasedVec { + pub fn unpack(self) -> TypeErasedVec { + self.0 + } +} + +// SAFETY: We ensure this type nevery contains any elements and only allow converting back to an empty TypeErasedVec +unsafe impl Send for SendableTypeErasedVec {} + +// SAFETY: We ensure this type nevery contains any elements and only allow converting back to an empty TypeErasedVec +unsafe impl Sync for SendableTypeErasedVec {}