diff --git a/src/lib.rs b/src/lib.rs index 74b99ff..f0c125b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,142 +3,46 @@ #[cfg(test)] mod tests; +mod guard; +mod send; +mod vtable; + +pub use guard::ContentGuard; +pub use send::SendableTypeErasedVec; + use std::alloc::Layout; -use std::marker::PhantomData; -use std::mem::ManuallyDrop; use std::ptr::NonNull; +use vtable::TypeErasedVecVtable; #[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, +pub(crate) struct VecParts { + pub ptr: NonNull, + pub len: usize, + pub 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 VecParts { + pub(crate) fn new(vec: Vec) -> Self { + let (ptr, len, cap) = vec.into_raw_parts(); -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) }; - } + // SAFETY: Vec guarantees its underlying pointer is non-null. + let ptr = unsafe { NonNull::new_unchecked(ptr.cast::()) }; - 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::, - } + Self { ptr, len, cap } } } /// 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, - vtable: TypeErasedVecVtable, -} + /// Raw parts of the erased vector + pub(crate) parts: VecParts, -/// 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 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(); - // 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.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) } - } + /// Vtable functions for generic operations on the erased vector + pub(crate) vtable: TypeErasedVecVtable, } impl TypeErasedVec { @@ -147,52 +51,9 @@ impl TypeErasedVec { /// Conversion back to a `Vec` is only allowed for types with the same Layout. #[must_use] pub fn new(vec: Vec) -> 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) - } - } - - 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 { - ptr, - cap, - len, - layout, + parts: VecParts::new(vec), + layout: Layout::new::(), vtable: TypeErasedVecVtable::new::(), } } @@ -200,16 +61,13 @@ impl TypeErasedVec { /// 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; + self.parts = unsafe { (self.vtable.clear_vector)(self.parts) }; } /// 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); - } + self.parts = unsafe { (self.vtable.reserve_vector)(self.parts, additional) }; } #[must_use] @@ -219,22 +77,22 @@ impl TypeErasedVec { #[must_use] pub fn capacity(&self) -> usize { - self.cap + self.parts.cap } #[must_use] pub fn length(&self) -> usize { - self.len + self.parts.len } #[must_use] pub fn is_empty(&self) -> bool { - self.len == 0 + self.parts.len == 0 } #[must_use] pub fn capacity_bytes(&self) -> usize { - self.cap * self.layout.size() + self.parts.cap * self.layout.size() } /// Access the erased capacity with a temporarily fixed type. @@ -248,46 +106,11 @@ impl TypeErasedVec { return None; } - // Overwrite the function pointers to target the new type. + // Overwrite the vtable completely 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) }; - } + self.vtable = TypeErasedVecVtable::new::(); - 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, - _phantom: PhantomData, - }) + Some(ContentGuard::new(self)) } /// Access the erased capacity with a temporarily fixed type. @@ -316,6 +139,7 @@ impl TypeErasedVec { 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 `to_type_unchecked` with an incompatible layout is UB! Target type layout must exactly match the erased layout. Capacity is reserved for {:?} but {} has {:?}", @@ -360,10 +184,10 @@ impl TypeErasedVec { unsafe { self.to_type_unchecked().take() } } - /// Clear a type erased vec allowing it be safetly shared across threads, preserving the capacity + /// Clear a type erased vec allowing it be safely shared across threads, preserving the capacity pub fn send(mut self) -> SendableTypeErasedVec { self.clear(); - SendableTypeErasedVec(self) + SendableTypeErasedVec::new(self) } } @@ -371,21 +195,7 @@ 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); + (self.vtable.drop_vector)(self.parts); } } } - -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 {}