extend (broken)

This commit is contained in:
2026-07-26 13:53:11 +02:00
parent c922684fbe
commit 9447c8a2cb
+184 -67
View File
@@ -4,7 +4,7 @@
mod tests;
use std::alloc::Layout;
use std::hint::unreachable_unchecked;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::ptr::NonNull;
@@ -12,57 +12,140 @@ use std::ptr::NonNull;
pub struct TypeErasedVec {
/// Pointer to the underlying allocation
ptr: NonNull<u8>,
/// Number of elements in the underlying vec
len: usize,
/// Size of the underlying allocation
capacity: usize,
cap: usize,
/// 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.
/// While we never drop any elements with it, we need to drop the capacity
/// by converting it back to an (empty) Vec of a compatibly type.
drop_capacity: unsafe fn(NonNull<u8>, usize),
drop_vector: unsafe fn(NonNull<u8>, usize, usize),
}
// SAFETY: TypeErasedVec only holds an empty memory allocation and a stateless function pointer.
// All elements are explicitly dropped during construction so no instances of the original type exist.
// Transferring ownership of this uninitialized capacity across threads is safe because the underlying
// global allocator is thread-safe and there is no data to cause data races.
unsafe impl Send for TypeErasedVec {}
/// Provides access to a `TypeErasedVec` with a temporarily fixed type `T`
pub struct ContentGuard<'vec, T> {
erased: &'vec mut TypeErasedVec,
_phantom: PhantomData<T>,
}
// SAFETY: TypeErasedVec contains no interior mutability.
// It only exposes immutable metadata regarding the underlying allocation when accessed via a shared reference.
// Sharing a reference to this empty allocation across threads cannot cause data races or undefined behavior.
unsafe impl Sync for TypeErasedVec {}
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);
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?
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] {
unsafe { std::slice::from_raw_parts(self.erased.ptr.as_ptr().cast(), self.erased.len) }
}
pub fn as_slice_mut(&mut self) -> &mut [T] {
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.
/// Conversion back to a `Vec` is only allowed for types with the same Layout.
#[must_use]
pub fn new<T>(mut vec: Vec<T>) -> Self {
// Define a cleanup function bound to the original type.
unsafe fn drop_vec_capacity<T>(ptr: NonNull<u8>, cap: usize) {
let _ = unsafe { Vec::from_raw_parts(ptr.as_ptr().cast::<T>(), 0, cap) };
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) {
_ = 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) {
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);
}
}
// 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) {
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)
}
}
let layout = Layout::new::<T>();
// Ensure the vector is empty. We don't do type casts, we just reuse capacity.
vec.clear();
// Deconstruct the original vector into its raw components.
let (ptr, len, capacity) = vec.into_raw_parts();
debug_assert_eq!(len, 0);
// Vec guarantees its pointer is never null, even when capacity is zero.
let (ptr, len, cap) = vec.into_raw_parts();
let ptr = unsafe { NonNull::new_unchecked(ptr.cast::<u8>()) };
Self {
ptr,
capacity,
cap,
len,
layout,
drop_capacity: drop_vec_capacity::<T>,
reserve_vector: reserve_vec::<T>,
clear_vector: clear_vec::<T>,
drop_vector: drop_vec::<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
pub fn clear(&mut self) {
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
pub fn reserve(&mut self, additional: usize) {
unsafe {
(self.ptr, self.cap) = (self.reserve_vector)(self.ptr, self.len, self.cap, additional);
}
}
@@ -75,13 +158,75 @@ impl TypeErasedVec {
/// Get the underlying capacity in units of the `Layout` size
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
self.cap
}
#[must_use]
pub fn length(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
/// Get the underlying capacity in bytes
#[must_use]
pub fn capacity_bytes(&self) -> usize {
self.capacity * self.layout.size()
self.cap * self.layout.size()
}
/// 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
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
Some(ContentGuard {
erased: self,
_phantom: PhantomData,
})
}
/// Access the erased capacity with a temporarily fixed type.
/// Clears all elements currently stored in the `TypeErasedVector`
///
pub fn try_to_type<'vec, T>(&'vec mut self) -> Option<ContentGuard<'vec, T>> {
self.clear();
unsafe { self.try_cast_type() }
}
pub fn to_type<'vec, T>(&'vec mut self) -> ContentGuard<'vec, T> {
let layout = self.layout;
self.try_to_type().unwrap_or_else(|| {
panic!(
"Target type layout must exactly match the erased layout. Capacity is reserved for {:?} but {} has {:?}",
layout,
std::any::type_name::<T>(),
Layout::new::<T>()
)
})
}
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 {:?}",
layout,
std::any::type_name::<T>(),
Layout::new::<T>()
)
}
unsafe { res.unwrap_unchecked() }
}
/// Convert the capacity of the erased `Vec` into a `Vec<T>`.
@@ -89,14 +234,9 @@ impl TypeErasedVec {
/// # 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`.
pub fn try_into_vec<T>(self) -> Result<Vec<T>, Self> {
if self.layout == Layout::new::<T>() {
// Wrap self in ManuallyDrop to bypass our custom Drop implementation.
// This transfers ownership of the memory to the new Vec.
let this = ManuallyDrop::new(self);
let vec = unsafe { Vec::from_raw_parts(this.ptr.as_ptr().cast(), 0, this.capacity) };
Ok(vec)
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())
} else {
Err(self)
}
@@ -107,18 +247,8 @@ impl TypeErasedVec {
/// # Panics
/// If `T` does not have the same `Layout` as the underlying capacity.
#[must_use]
pub fn into_vec<T>(self) -> Vec<T> {
match self.try_into_vec::<T>() {
Ok(vec) => vec,
Err(this) => {
panic!(
"Target type layout must exactly match the erased layout. Capacity is reserved for {:?} but {} has {:?}",
this.layout,
std::any::type_name::<T>(),
Layout::new::<T>()
)
}
}
pub fn into_vec<T>(mut self) -> Vec<T> {
self.to_type::<T>().take()
}
/// Convert the capacity of the erased `Vec` into a `Vec<T>`.
@@ -126,28 +256,15 @@ impl TypeErasedVec {
/// # Safety
/// `T` must have the same `Layout` as the underlying capacity.
#[must_use]
pub unsafe fn into_vec_unchecked<T>(self) -> Vec<T> {
match self.try_into_vec::<T>() {
Ok(vec) => vec,
Err(this) => {
if cfg!(debug_assertions) {
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 {:?}",
this.layout,
std::any::type_name::<T>(),
Layout::new::<T>()
)
}
unsafe { unreachable_unchecked() }
}
}
pub unsafe fn into_vec_unchecked<T>(mut self) -> Vec<T> {
unsafe { self.to_type_unchecked().take() }
}
}
impl Drop for TypeErasedVec {
fn drop(&mut self) {
unsafe {
(self.drop_capacity)(self.ptr, self.capacity);
(self.drop_vector)(self.ptr, self.len, self.cap);
}
}
}