diff --git a/src/guard.rs b/src/guard.rs new file mode 100644 index 0000000..d98aafe --- /dev/null +++ b/src/guard.rs @@ -0,0 +1,82 @@ +use crate::TypeErasedVec; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; + +/// Provides access to a `TypeErasedVec` with a temporarily fixed type `T` +pub struct ContentGuard<'vec, T> { + erased: &'vec mut TypeErasedVec, + _phantom: PhantomData, +} + +impl<'vec, T> ContentGuard<'vec, T> { + pub(crate) fn new(erased: &'vec mut TypeErasedVec) -> Self { + Self { + erased, + _phantom: PhantomData, + } + } + + 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.parts.ptr.as_ptr().cast::(), + erased.parts.len, + erased.parts.cap, + ) + } + } + + pub fn with(&mut self, f: impl FnOnce(&mut Vec) -> R) -> R { + let mut vec = self.take(); + // 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 + } + + pub fn clear(&mut self) { + self.with(|vec| vec.clear()); + } + + pub fn reserve(&mut self, additional: usize) { + self.with(|vec| vec.reserve(additional)); + } + + #[must_use] + pub fn capacity(&self) -> usize { + self.erased.capacity() + } + + #[must_use] + pub fn length(&self) -> usize { + self.erased.length() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.erased.is_empty() + } + + 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.parts.ptr.as_ptr().cast(), self.erased.parts.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.parts.ptr.as_ptr().cast(), + self.erased.parts.len, + ) + } + } +} diff --git a/src/send.rs b/src/send.rs new file mode 100644 index 0000000..9ac6579 --- /dev/null +++ b/src/send.rs @@ -0,0 +1,19 @@ +use crate::TypeErasedVec; + +pub struct SendableTypeErasedVec(TypeErasedVec); + +impl SendableTypeErasedVec { + pub(crate) fn new(vec: TypeErasedVec) -> Self { + Self(vec) + } + + pub fn unpack(self) -> TypeErasedVec { + self.0 + } +} + +// SAFETY: We ensure this type never contains any elements and only allow converting back to an empty TypeErasedVec +unsafe impl Send for SendableTypeErasedVec {} + +// SAFETY: We ensure this type never contains any elements and only allow converting back to an empty TypeErasedVec +unsafe impl Sync for SendableTypeErasedVec {} diff --git a/src/vtable.rs b/src/vtable.rs new file mode 100644 index 0000000..239571c --- /dev/null +++ b/src/vtable.rs @@ -0,0 +1,66 @@ +use crate::VecParts; +use std::ptr::NonNull; + +pub(crate) struct TypeErasedVecVtable { + /// Function pointer to reserve additional capacity in the underlying Vec + pub reserve_vector: unsafe fn(VecParts, usize) -> VecParts, + /// Function pointer to clear the underlying capacity of its old elements + pub clear_vector: unsafe fn(VecParts) -> VecParts, + /// Function pointer to the original type's drop logic. + pub drop_vector: unsafe fn(VecParts), +} + +impl TypeErasedVecVtable { + pub fn new() -> Self { + unsafe fn drop_vec(parts: VecParts) { + // SAFETY: We reconstruct the Vec to let its Drop impl handle deallocation. + _ = unsafe { + Vec::from_raw_parts(parts.ptr.as_ptr().cast::(), parts.len, parts.cap) + }; + } + + unsafe fn clear_vec(parts: VecParts) -> VecParts { + // 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(parts.ptr.as_ptr().cast::(), parts.len, parts.cap); + vec.clear(); + let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec); + debug_assert!(std::ptr::eq(new_ptr, parts.ptr.as_ptr().cast())); + debug_assert_eq!(new_cap, parts.cap); + debug_assert_eq!(new_len, 0); + + VecParts { + ptr: NonNull::new_unchecked(new_ptr).cast(), + len: new_len, + cap: new_cap, + } + } + } + + unsafe fn reserve_vec(parts: VecParts, additional: usize) -> VecParts { + // SAFETY: We reconstruct the Vec to trigger a reserve. + // Capacity might change upon reallocation. + unsafe { + let mut vec = + Vec::from_raw_parts(parts.ptr.as_ptr().cast::(), parts.len, parts.cap); + vec.reserve(additional); + let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec); + debug_assert_eq!(new_len, parts.len); + + VecParts { + ptr: NonNull::new_unchecked(new_ptr).cast(), + len: new_len, + cap: new_cap, + } + } + } + + Self { + reserve_vector: reserve_vec::, + clear_vector: clear_vec::, + drop_vector: drop_vec::, + } + } +}