start refactor (broken)

This commit is contained in:
2026-07-26 14:03:24 +02:00
parent 9447c8a2cb
commit cf01541515
+153 -32
View File
@@ -8,23 +8,78 @@ use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::ptr::NonNull;
/// 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 {
#[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,
}
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<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)
}
}
Self {
reserve_vector: reserve_vec::<T>,
clear_vector: clear_vec::<T>,
drop_vector: drop_vec::<T>,
}
}
}
/// 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,
/// Function pointer to reserve additional capacity in the underlying Vec
reserve_vector: unsafe fn(NonNull<u8>, usize, usize, usize) -> (NonNull<u8>, usize),
/// Function pointer to clear the underlying capacity of its old elements
clear_vector: unsafe fn(NonNull<u8>, usize, usize),
/// Function pointer to the original type's drop logic.
drop_vector: unsafe fn(NonNull<u8>, 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<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();
// todo: is this panic / unwind safe?
// 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
@@ -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<T>(vec: Vec<T>) -> Self {
// Define a drop function bound to the original type.
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) };
}
// Define a clear function bound to the original type.
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();
@@ -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<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_cap, cap);
debug_assert_eq!(new_len, len);
(NonNull::new_unchecked(new_ptr).cast::<u8>(), new_cap)
}
@@ -121,6 +185,7 @@ impl TypeErasedVec {
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 {
@@ -128,34 +193,30 @@ impl TypeErasedVec {
cap,
len,
layout,
reserve_vector: reserve_vec::<T>,
clear_vector: clear_vec::<T>,
drop_vector: drop_vec::<T>,
vtable: TypeErasedVecVtable::new::<T>(),
}
}
/// Clear any remaining elements in the vector, dropping them
/// Calls `Vec::<T>::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::<T>::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<ContentGuard<'vec, T>> {
if self.layout != Layout::new::<T>() {
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<T>(ptr: NonNull<u8>, len: usize, cap: usize) {
_ = unsafe { Vec::from_raw_parts(ptr.as_ptr().cast::<T>(), len, cap) };
}
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,
@@ -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<ContentGuard<'vec, T>> {
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::<T>(),
Layout::new::<T>()
)
}
// SAFETY: Ensured by the caller.
unsafe { res.unwrap_unchecked() }
}
/// Convert the capacity of the erased `Vec` into a `Vec<T>`.
///
/// # 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<T>(mut self) -> Result<Vec<T>, Self> {
if let Some(mut guard) = self.try_to_type::<T>() {
Ok(guard.take())
@@ -245,7 +344,7 @@ impl TypeErasedVec {
/// Convert the capacity of the erased `Vec` into a `Vec<T>`.
///
/// # 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<T>(mut self) -> Vec<T> {
self.to_type::<T>().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<T>(mut self) -> Vec<T> {
// 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 {}