start refactor (broken)
This commit is contained in:
+153
-32
@@ -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 {}
|
||||
|
||||
Reference in New Issue
Block a user