continue refactor

This commit is contained in:
2026-07-26 14:07:24 +02:00
parent cf01541515
commit 08113c8288
+37 -227
View File
@@ -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<u8>,
/// Number of elements in the underlying vec
len: usize,
/// Size of the underlying allocation
cap: usize,
pub(crate) struct VecParts {
pub ptr: NonNull<u8>,
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<T>(vec: Vec<T>) -> Self {
let (ptr, len, cap) = vec.into_raw_parts();
impl TypeErasedVecVtable {
fn new<T>() -> Self {
unsafe fn drop_vec<T>(ptr: NonNull<u8>, 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::<T>(), len, cap) };
}
// SAFETY: Vec guarantees its underlying pointer is non-null.
let ptr = unsafe { NonNull::new_unchecked(ptr.cast::<u8>()) };
unsafe fn clear_vec<T>(ptr: NonNull<u8>, 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::<T>(), 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<T>(
ptr: NonNull<u8>,
len: usize,
cap: usize,
additional: usize,
) -> (NonNull<u8>, 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::<T>(), 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::<u8>(), new_cap)
}
}
Self {
reserve_vector: reserve_vec::<T>,
clear_vector: clear_vec::<T>,
drop_vector: drop_vec::<T>,
}
Self { ptr, len, cap }
}
}
/// Stores the capacity of a `Vec<U>` for later reuse as a `Vec<V>` 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<T>,
}
impl<'vec, T> ContentGuard<'vec, T> {
pub fn take(&mut self) -> Vec<T> {
let erased = std::mem::replace(self.erased, TypeErasedVec::new(Vec::<T>::new()));
let erased = ManuallyDrop::new(erased);
// SAFETY: The erased pointer, length, and capacity are valid for a Vec<T>.
// ManuallyDrop prevents the old TypeErasedVec from double-freeing the allocation.
unsafe { Vec::from_raw_parts(erased.ptr.as_ptr().cast::<T>(), erased.len, erased.cap) }
}
pub fn with<R>(&mut self, f: impl FnOnce(&mut Vec<T>) -> R) -> R {
let mut vec = self.take();
// This is unwind-safe. If the closure panics, the inner `Vec<T>` 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<T>(vec: Vec<T>) -> Self {
unsafe fn drop_vec<T>(ptr: NonNull<u8>, 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::<T>(), len, cap) };
}
unsafe fn clear_vec<T>(ptr: NonNull<u8>, 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::<T>(), 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<T>(
ptr: NonNull<u8>,
len: usize,
cap: usize,
additional: usize,
) -> (NonNull<u8>, 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::<T>(), 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::<u8>(), new_cap)
}
}
let layout = Layout::new::<T>();
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::<u8>()) };
Self {
ptr,
cap,
len,
layout,
parts: VecParts::new(vec),
layout: Layout::new::<T>(),
vtable: TypeErasedVecVtable::new::<T>(),
}
}
@@ -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<T>(ptr: NonNull<u8>, len: usize, cap: usize) {
_ = unsafe { Vec::from_raw_parts(ptr.as_ptr().cast::<T>(), len, cap) };
}
self.vtable = TypeErasedVecVtable::new::<T>();
unsafe fn clear_vec<T>(ptr: NonNull<u8>, len: usize, cap: usize) {
unsafe {
let mut vec = Vec::from_raw_parts(ptr.as_ptr().cast::<T>(), 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<T>(
ptr: NonNull<u8>,
len: usize,
cap: usize,
additional: usize,
) -> (NonNull<u8>, usize) {
unsafe {
let mut vec = Vec::from_raw_parts(ptr.as_ptr().cast::<T>(), 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::<u8>(), new_cap)
}
}
self.drop_vector = drop_vec::<T>;
self.clear_vector = clear_vec::<T>;
self.reserve_vector = reserve_vec::<T>;
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 {}