continue refactor
This commit is contained in:
+4
-8
@@ -21,13 +21,7 @@ impl<'vec, T> ContentGuard<'vec, T> {
|
||||
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.parts.ptr.as_ptr().cast::<T>(),
|
||||
erased.parts.len,
|
||||
erased.parts.cap,
|
||||
)
|
||||
}
|
||||
unsafe { erased.parts.into_vec() }
|
||||
}
|
||||
|
||||
pub fn with<R>(&mut self, f: impl FnOnce(&mut Vec<T>) -> R) -> R {
|
||||
@@ -41,7 +35,7 @@ impl<'vec, T> ContentGuard<'vec, T> {
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.with(|vec| vec.clear());
|
||||
self.with(Vec::clear);
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
@@ -63,6 +57,7 @@ impl<'vec, T> ContentGuard<'vec, T> {
|
||||
self.erased.is_empty()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
// SAFETY: The pointer and length correctly represent the currently initialized elements.
|
||||
unsafe {
|
||||
@@ -70,6 +65,7 @@ impl<'vec, T> ContentGuard<'vec, T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn as_slice_mut(&mut self) -> &mut [T] {
|
||||
// SAFETY: The pointer and length correctly represent the currently initialized elements.
|
||||
unsafe {
|
||||
|
||||
+31
-18
@@ -22,7 +22,7 @@ pub(crate) struct VecParts {
|
||||
}
|
||||
|
||||
impl VecParts {
|
||||
pub(crate) fn new<T>(vec: Vec<T>) -> Self {
|
||||
pub(crate) fn from_vec<T>(vec: Vec<T>) -> Self {
|
||||
let (ptr, len, cap) = vec.into_raw_parts();
|
||||
|
||||
// SAFETY: Vec guarantees its underlying pointer is non-null.
|
||||
@@ -30,6 +30,10 @@ impl VecParts {
|
||||
|
||||
Self { ptr, len, cap }
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn into_vec<T>(self) -> Vec<T> {
|
||||
unsafe { Vec::from_raw_parts(self.ptr.as_ptr().cast::<T>(), self.len, self.cap) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the capacity of a `Vec<U>` for later reuse as a `Vec<V>` where `V` shares the same `Layout` as `U`.
|
||||
@@ -52,7 +56,7 @@ impl TypeErasedVec {
|
||||
#[must_use]
|
||||
pub fn new<T>(vec: Vec<T>) -> Self {
|
||||
Self {
|
||||
parts: VecParts::new(vec),
|
||||
parts: VecParts::from_vec(vec),
|
||||
layout: Layout::new::<T>(),
|
||||
vtable: TypeErasedVecVtable::new::<T>(),
|
||||
}
|
||||
@@ -61,13 +65,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.
|
||||
self.parts = unsafe { (self.vtable.clear_vector)(self.parts) };
|
||||
self.parts = unsafe { (self.vtable.clear)(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.
|
||||
self.parts = unsafe { (self.vtable.reserve_vector)(self.parts, additional) };
|
||||
self.parts = unsafe { (self.vtable.reserve)(self.parts, additional) };
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -101,28 +105,35 @@ impl TypeErasedVec {
|
||||
/// # 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;
|
||||
}
|
||||
///
|
||||
/// The `Layout` of the new type `T` does not match the `Layout` of the allocated capacity
|
||||
pub unsafe fn cast_type<T>(&mut self) -> ContentGuard<'_, T> {
|
||||
debug_assert_eq!(self.layout, Layout::new::<T>());
|
||||
|
||||
// Overwrite the vtable completely to target the new type.
|
||||
// This guarantees that any subsequent drop or clear invokes the correct destructors.
|
||||
self.vtable = TypeErasedVecVtable::new::<T>();
|
||||
|
||||
Some(ContentGuard::new(self))
|
||||
ContentGuard::new(self)
|
||||
}
|
||||
|
||||
/// Access the erased capacity with a temporarily fixed type.
|
||||
/// Clears all elements currently stored in the `TypeErasedVec`.
|
||||
pub fn try_to_type<'vec, T>(&'vec mut self) -> Option<ContentGuard<'vec, T>> {
|
||||
///
|
||||
/// Will fail if `T` does not have the same `Layout` as the underlying capacity.
|
||||
pub fn try_to_type<T>(&mut self) -> Option<ContentGuard<'_, 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() }
|
||||
(self.layout == Layout::new::<T>()).then(|| ContentGuard::new(self))
|
||||
}
|
||||
|
||||
pub fn to_type<'vec, T>(&'vec mut self) -> ContentGuard<'vec, T> {
|
||||
/// Access the erased capacity with a temporarily fixed type.
|
||||
/// Clears all elements currently stored in the `TypeErasedVec`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if `T` does not have the same `Layout` as the underlying capacity.
|
||||
pub fn to_type<T>(&mut self) -> ContentGuard<'_, T> {
|
||||
let layout = self.layout;
|
||||
self.try_to_type().unwrap_or_else(|| {
|
||||
panic!(
|
||||
@@ -134,9 +145,12 @@ impl TypeErasedVec {
|
||||
})
|
||||
}
|
||||
|
||||
/// Access the erased capacity with a temporarily fixed type.
|
||||
/// Clears all elements currently stored in the `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> {
|
||||
/// `T` must have the same `Layout` as the underlying capacity.
|
||||
pub unsafe fn to_type_unchecked<T>(&mut self) -> ContentGuard<'_, T> {
|
||||
let layout = self.layout;
|
||||
let res = self.try_to_type();
|
||||
|
||||
@@ -185,17 +199,16 @@ impl TypeErasedVec {
|
||||
}
|
||||
|
||||
/// Clear a type erased vec allowing it be safely shared across threads, preserving the capacity
|
||||
#[must_use]
|
||||
pub fn send(mut self) -> SendableTypeErasedVec {
|
||||
self.clear();
|
||||
SendableTypeErasedVec::new(self)
|
||||
unsafe { SendableTypeErasedVec::from_empty(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TypeErasedVec {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: The correct drop vector executes the destructors and frees the allocation.
|
||||
unsafe {
|
||||
(self.vtable.drop_vector)(self.parts);
|
||||
}
|
||||
unsafe { (self.vtable.drop)(self.parts) };
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -3,10 +3,14 @@ use crate::TypeErasedVec;
|
||||
pub struct SendableTypeErasedVec(TypeErasedVec);
|
||||
|
||||
impl SendableTypeErasedVec {
|
||||
pub(crate) fn new(vec: TypeErasedVec) -> Self {
|
||||
/// # SAFETY
|
||||
/// the `vec` must not contain any initialized elements
|
||||
pub(crate) unsafe fn from_empty(vec: TypeErasedVec) -> Self {
|
||||
debug_assert!(vec.is_empty());
|
||||
Self(vec)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn unpack(self) -> TypeErasedVec {
|
||||
self.0
|
||||
}
|
||||
|
||||
+253
-118
@@ -1,165 +1,300 @@
|
||||
use super::*;
|
||||
use std::alloc::Layout;
|
||||
use std::cell::Cell;
|
||||
use std::panic;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[test]
|
||||
fn test_basic_roundtrip() {
|
||||
let mut v = Vec::<i32>::with_capacity(10);
|
||||
v.push(1);
|
||||
v.push(2);
|
||||
let cap = v.capacity();
|
||||
struct DropTracker {
|
||||
counter: Rc<Cell<usize>>,
|
||||
}
|
||||
|
||||
let erased = TypeErasedVec::new(v);
|
||||
let recovered: Vec<i32> = erased.into_vec();
|
||||
impl DropTracker {
|
||||
fn new(counter: Rc<Cell<usize>>) -> Self {
|
||||
Self { counter }
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(recovered.len(), 0);
|
||||
assert_eq!(recovered.capacity(), cap);
|
||||
impl Drop for DropTracker {
|
||||
fn drop(&mut self) {
|
||||
self.counter.set(self.counter.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_erased_state(
|
||||
erased: &TypeErasedVec,
|
||||
expected_len: usize,
|
||||
expected_min_cap: usize,
|
||||
expected_layout: Layout,
|
||||
) {
|
||||
assert_eq!(erased.length(), expected_len, "Length mismatch");
|
||||
assert!(erased.capacity() >= expected_min_cap, "Capacity too small");
|
||||
assert_eq!(erased.is_empty(), expected_len == 0, "is_empty mismatch");
|
||||
assert_eq!(erased.layout(), expected_layout, "Layout mismatch");
|
||||
assert_eq!(
|
||||
erased.capacity_bytes(),
|
||||
erased.capacity() * expected_layout.size(),
|
||||
"Capacity bytes mismatch"
|
||||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_guard_state<T>(
|
||||
guard: &ContentGuard<'_, T>,
|
||||
expected_len: usize,
|
||||
expected_min_cap: usize,
|
||||
) {
|
||||
assert_eq!(guard.length(), expected_len, "Guard length mismatch");
|
||||
assert!(
|
||||
guard.capacity() >= expected_min_cap,
|
||||
"Guard capacity too small"
|
||||
);
|
||||
assert_eq!(
|
||||
guard.is_empty(),
|
||||
expected_len == 0,
|
||||
"Guard is_empty mismatch"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compatible_types() {
|
||||
let mut v = Vec::<u32>::with_capacity(42);
|
||||
v.push(100);
|
||||
let cap = v.capacity();
|
||||
fn test_sendable_is_send_sync() {
|
||||
fn assert_send<T: Send>() {}
|
||||
fn assert_sync<T: Sync>() {}
|
||||
|
||||
let erased = TypeErasedVec::new(v);
|
||||
|
||||
// Convert to a different type with the exact same Layout
|
||||
let recovered: Vec<f32> = erased.try_into_vec().ok().unwrap();
|
||||
|
||||
assert_eq!(recovered.len(), 0);
|
||||
assert_eq!(recovered.capacity(), cap);
|
||||
assert_send::<SendableTypeErasedVec>();
|
||||
assert_sync::<SendableTypeErasedVec>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incompatible_size() {
|
||||
let v = Vec::<u64>::with_capacity(10);
|
||||
let erased = TypeErasedVec::new(v);
|
||||
fn test_new_and_basic_properties() {
|
||||
let mut vec = Vec::<u32>::with_capacity(10);
|
||||
vec.extend_from_slice(&[1, 2, 3, 4, 5]);
|
||||
let initial_cap = vec.capacity();
|
||||
|
||||
let result: Result<Vec<u8>, TypeErasedVec> = erased.try_into_vec();
|
||||
let erased = TypeErasedVec::new(vec);
|
||||
|
||||
// Result should be Err containing the original erased vec
|
||||
let Err(erased) = result else {
|
||||
panic!("Expected conversion from u64 to u8 to fail");
|
||||
assert_erased_state(&erased, 5, initial_cap, Layout::new::<u32>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear_drops_elements() {
|
||||
let drop_count = Rc::new(Cell::new(0));
|
||||
let mut vec = Vec::with_capacity(5);
|
||||
|
||||
for _ in 0..5 {
|
||||
vec.push(DropTracker::new(drop_count.clone()));
|
||||
}
|
||||
|
||||
let mut erased = TypeErasedVec::new(vec);
|
||||
assert_erased_state(&erased, 5, 5, Layout::new::<DropTracker>());
|
||||
assert_eq!(drop_count.get(), 0);
|
||||
|
||||
erased.clear();
|
||||
|
||||
assert_erased_state(&erased, 0, 5, Layout::new::<DropTracker>());
|
||||
assert_eq!(drop_count.get(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reserve_increases_capacity() {
|
||||
let vec = Vec::<u64>::with_capacity(2);
|
||||
let mut erased = TypeErasedVec::new(vec);
|
||||
|
||||
assert_erased_state(&erased, 0, 2, Layout::new::<u64>());
|
||||
|
||||
erased.reserve(100);
|
||||
|
||||
assert_erased_state(&erased, 0, 100, Layout::new::<u64>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_cast_type_success_and_mutation() {
|
||||
let mut vec = Vec::<i32>::with_capacity(10);
|
||||
vec.push(-1);
|
||||
vec.push(-2);
|
||||
let initial_cap = vec.capacity();
|
||||
|
||||
let mut erased = TypeErasedVec::new(vec);
|
||||
assert_erased_state(&erased, 2, initial_cap, Layout::new::<i32>());
|
||||
|
||||
let mut guard = unsafe { erased.try_cast_type::<u32>().expect("Layouts match") };
|
||||
assert_guard_state(&guard, 2, initial_cap);
|
||||
|
||||
let slice = guard.as_slice_mut();
|
||||
slice[0] = 42;
|
||||
|
||||
let slice = guard.as_slice();
|
||||
assert_eq!(slice, &[42, 4294967294]);
|
||||
|
||||
let restored = guard.take();
|
||||
assert_eq!(restored, vec![42, 4294967294]);
|
||||
assert_guard_state(&guard, 0, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_cast_type_failure() {
|
||||
let vec = Vec::<u32>::with_capacity(5);
|
||||
let initial_cap = vec.capacity();
|
||||
let mut erased = TypeErasedVec::new(vec);
|
||||
|
||||
assert_erased_state(&erased, 0, initial_cap, Layout::new::<u32>());
|
||||
|
||||
let guard = unsafe { erased.try_cast_type::<u64>() };
|
||||
assert!(guard.is_none(), "Should fail due to layout mismatch");
|
||||
assert_erased_state(&erased, 0, initial_cap, Layout::new::<u32>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_to_type_clears_elements() {
|
||||
let drop_count = Rc::new(Cell::new(0));
|
||||
let mut vec = Vec::with_capacity(5);
|
||||
|
||||
for _ in 0..3 {
|
||||
vec.push(DropTracker::new(drop_count.clone()));
|
||||
}
|
||||
|
||||
let mut erased = TypeErasedVec::new(vec);
|
||||
assert_erased_state(&erased, 3, 5, Layout::new::<DropTracker>());
|
||||
|
||||
let guard = erased.try_to_type::<DropTracker>().expect("Layouts match");
|
||||
|
||||
assert_guard_state(&guard, 0, 5);
|
||||
assert_eq!(drop_count.get(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_guard_with_unwind_safety() {
|
||||
let drop_count = Rc::new(Cell::new(0));
|
||||
let mut vec = Vec::with_capacity(10);
|
||||
vec.push(DropTracker::new(drop_count.clone()));
|
||||
|
||||
let mut erased = TypeErasedVec::new(vec);
|
||||
assert_erased_state(&erased, 1, 10, Layout::new::<DropTracker>());
|
||||
|
||||
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||
let mut guard = unsafe { erased.try_cast_type::<DropTracker>().unwrap() };
|
||||
guard.with(|v| {
|
||||
v.push(DropTracker::new(drop_count.clone()));
|
||||
panic!("Simulated panic inside with()");
|
||||
});
|
||||
}));
|
||||
|
||||
assert!(res.is_err());
|
||||
|
||||
assert_erased_state(&erased, 0, 0, Layout::new::<DropTracker>());
|
||||
assert_eq!(drop_count.get(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conversion_methods() {
|
||||
let mut vec = Vec::<String>::with_capacity(5);
|
||||
vec.push("test".to_string());
|
||||
let initial_cap = vec.capacity();
|
||||
|
||||
let erased = TypeErasedVec::new(vec);
|
||||
assert_erased_state(&erased, 1, initial_cap, Layout::new::<String>());
|
||||
|
||||
let Ok(restored) = erased.try_into_vec::<String>() else {
|
||||
panic!("try_into_vec failed despite matching layouts");
|
||||
};
|
||||
|
||||
assert_eq!(erased.capacity(), 10);
|
||||
assert_eq!(erased.into_vec::<u64>().capacity(), 10);
|
||||
assert_eq!(restored.len(), 0);
|
||||
assert!(restored.capacity() >= initial_cap);
|
||||
|
||||
let mut erased2 = TypeErasedVec::new(restored);
|
||||
let mut guard = erased2.to_type::<String>();
|
||||
guard.reserve(20);
|
||||
let new_cap = guard.capacity();
|
||||
|
||||
let restored2 = guard.take();
|
||||
assert_eq!(restored2.capacity(), new_cap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incompatible_alignment() {
|
||||
#[repr(align(16))]
|
||||
struct Align16(#[allow(unused)] u8);
|
||||
fn test_try_into_vec_failure_and_recovery() {
|
||||
let vec = Vec::<u32>::with_capacity(5);
|
||||
let initial_cap = vec.capacity();
|
||||
|
||||
#[repr(align(8))]
|
||||
struct Align8(#[allow(unused)] u8);
|
||||
let erased = TypeErasedVec::new(vec);
|
||||
|
||||
let v = Vec::<Align16>::with_capacity(10);
|
||||
let erased = TypeErasedVec::new(v);
|
||||
|
||||
// Sizes might be compatible or both be wrapped in padding, but alignment differs
|
||||
let result: Result<Vec<Align8>, TypeErasedVec> = erased.try_into_vec();
|
||||
|
||||
let Err(erased) = result else {
|
||||
panic!("Expected conversion from u64 to u8 to fail");
|
||||
let Err(recovered_erased) = erased.try_into_vec::<u64>() else {
|
||||
panic!("try_into_vec succeeded with mismatched layout");
|
||||
};
|
||||
|
||||
assert_eq!(erased.capacity(), 10);
|
||||
assert_eq!(erased.into_vec::<Align16>().capacity(), 10);
|
||||
assert_erased_state(&recovered_erased, 0, initial_cap, Layout::new::<u32>());
|
||||
|
||||
let Ok(restored) = recovered_erased.try_into_vec::<u32>() else {
|
||||
panic!("try_into_vec failed after recovery");
|
||||
};
|
||||
|
||||
assert!(restored.capacity() >= initial_cap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Target type layout must exactly match")]
|
||||
fn test_into_vec_panic_on_mismatch() {
|
||||
let v = Vec::<u64>::with_capacity(10);
|
||||
let erased = TypeErasedVec::new(v);
|
||||
|
||||
// This should panic due to Layout mismatch
|
||||
let _panic: Vec<u8> = erased.into_vec();
|
||||
fn test_to_type_panics_on_layout_mismatch() {
|
||||
let vec = Vec::<u32>::new();
|
||||
let mut erased = TypeErasedVec::new(vec);
|
||||
let _ = erased.to_type::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_elements_are_dropped() {
|
||||
let counter = Rc::new(());
|
||||
let mut v = Vec::new();
|
||||
|
||||
v.push(Rc::clone(&counter));
|
||||
v.push(Rc::clone(&counter));
|
||||
|
||||
assert_eq!(Rc::strong_count(&counter), 3);
|
||||
|
||||
let erased = TypeErasedVec::new(v);
|
||||
|
||||
// Elements should have been dropped during `TypeErasedVec::new` by `vec.clear()`
|
||||
assert_eq!(Rc::strong_count(&counter), 1);
|
||||
|
||||
std::mem::drop(erased);
|
||||
|
||||
assert_eq!(Rc::strong_count(&counter), 1);
|
||||
#[should_panic(expected = "Target type layout must exactly match")]
|
||||
fn test_into_vec_panics_on_layout_mismatch() {
|
||||
let vec = Vec::<u32>::new();
|
||||
let erased = TypeErasedVec::new(vec);
|
||||
let _ = erased.into_vec::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zst_handling() {
|
||||
let mut v = Vec::<()>::with_capacity(10);
|
||||
v.push(());
|
||||
v.push(());
|
||||
|
||||
let erased = TypeErasedVec::new(v);
|
||||
let recovered: Vec<()> = erased.into_vec();
|
||||
|
||||
assert_eq!(recovered.len(), 0);
|
||||
#[cfg(debug_assertions)]
|
||||
#[should_panic(expected = "Calling `to_type_unchecked` with an incompatible layout is UB!")]
|
||||
fn test_to_type_unchecked_panics_on_layout_mismatch_in_debug() {
|
||||
let vec = Vec::<u32>::new();
|
||||
let mut erased = TypeErasedVec::new(vec);
|
||||
unsafe {
|
||||
let _ = erased.to_type_unchecked::<u64>();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_capacity() {
|
||||
let v = Vec::<i32>::new();
|
||||
let erased = TypeErasedVec::new(v);
|
||||
|
||||
let recovered: Vec<i32> = erased.into_vec();
|
||||
|
||||
assert_eq!(recovered.len(), 0);
|
||||
assert_eq!(recovered.capacity(), 0);
|
||||
#[cfg(debug_assertions)]
|
||||
#[should_panic(expected = "Calling `to_type_unchecked` with an incompatible layout is UB!")]
|
||||
fn test_into_vec_unchecked_panics_on_layout_mismatch_in_debug() {
|
||||
let vec = Vec::<u32>::new();
|
||||
let erased = TypeErasedVec::new(vec);
|
||||
unsafe {
|
||||
let _ = erased.into_vec_unchecked::<u64>();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_erased_memory_leak() {
|
||||
let v = Vec::<String>::with_capacity(100);
|
||||
let erased = TypeErasedVec::new(v);
|
||||
fn test_sendable_clears_and_unpacks() {
|
||||
let drop_count = Rc::new(Cell::new(0));
|
||||
let mut vec = Vec::with_capacity(5);
|
||||
vec.push(DropTracker::new(drop_count.clone()));
|
||||
|
||||
// Miri will flag this test as failing if `erased` doesn't properly deallocate
|
||||
// the underlying memory upon being dropped.
|
||||
drop(erased);
|
||||
let erased = TypeErasedVec::new(vec);
|
||||
assert_erased_state(&erased, 1, 5, Layout::new::<DropTracker>());
|
||||
|
||||
let sendable = erased.send();
|
||||
assert_eq!(drop_count.get(), 1);
|
||||
|
||||
let unpacked = sendable.unpack();
|
||||
assert_erased_state(&unpacked, 0, 5, Layout::new::<DropTracker>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_vec_unchecked_success() {
|
||||
let mut v = Vec::with_capacity(15);
|
||||
v.push(42);
|
||||
let cap = v.capacity();
|
||||
fn test_drop_cleans_up_allocation() {
|
||||
let drop_count = Rc::new(Cell::new(0));
|
||||
let mut vec = Vec::with_capacity(5);
|
||||
vec.push(DropTracker::new(drop_count.clone()));
|
||||
|
||||
let erased = TypeErasedVec::new(v);
|
||||
|
||||
let recovered: Vec<i32> = unsafe { erased.into_vec_unchecked() };
|
||||
|
||||
assert_eq!(recovered.len(), 0);
|
||||
assert_eq!(recovered.capacity(), cap);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_struct_drop() {
|
||||
struct Droppy {
|
||||
_a: String,
|
||||
_b: Vec<u8>,
|
||||
{
|
||||
let erased = TypeErasedVec::new(vec);
|
||||
assert_erased_state(&erased, 1, 5, Layout::new::<DropTracker>());
|
||||
assert_eq!(drop_count.get(), 0);
|
||||
}
|
||||
|
||||
let mut v = Vec::with_capacity(5);
|
||||
v.push(Droppy {
|
||||
_a: String::from("Hello"),
|
||||
_b: vec![1, 2, 3],
|
||||
});
|
||||
|
||||
let erased = TypeErasedVec::new(v);
|
||||
|
||||
// Ensures `TypeErasedVec` properly drops via the generic drop function
|
||||
drop(erased);
|
||||
assert_eq!(drop_count.get(), 1);
|
||||
}
|
||||
|
||||
+17
-30
@@ -3,39 +3,31 @@ use std::ptr::NonNull;
|
||||
|
||||
pub(crate) struct TypeErasedVecVtable {
|
||||
/// Function pointer to reserve additional capacity in the underlying Vec
|
||||
pub reserve_vector: unsafe fn(VecParts, usize) -> VecParts,
|
||||
pub reserve: unsafe fn(parts: VecParts, additional: usize) -> VecParts,
|
||||
/// Function pointer to clear the underlying capacity of its old elements
|
||||
pub clear_vector: unsafe fn(VecParts) -> VecParts,
|
||||
pub clear: unsafe fn(parts: VecParts) -> VecParts,
|
||||
/// Function pointer to the original type's drop logic.
|
||||
pub drop_vector: unsafe fn(VecParts),
|
||||
pub drop: unsafe fn(parts: VecParts),
|
||||
}
|
||||
|
||||
impl TypeErasedVecVtable {
|
||||
pub fn new<T>() -> Self {
|
||||
unsafe fn drop_vec<T>(parts: VecParts) {
|
||||
// SAFETY: We reconstruct the Vec to let its Drop impl handle deallocation.
|
||||
_ = unsafe {
|
||||
Vec::from_raw_parts(parts.ptr.as_ptr().cast::<T>(), parts.len, parts.cap)
|
||||
};
|
||||
_ = unsafe { parts.into_vec::<T>() };
|
||||
}
|
||||
|
||||
unsafe fn clear_vec<T>(parts: VecParts) -> VecParts {
|
||||
// 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(parts.ptr.as_ptr().cast::<T>(), parts.len, parts.cap);
|
||||
let mut vec = parts.into_vec::<T>();
|
||||
vec.clear();
|
||||
let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec);
|
||||
debug_assert!(std::ptr::eq(new_ptr, parts.ptr.as_ptr().cast()));
|
||||
debug_assert_eq!(new_cap, parts.cap);
|
||||
debug_assert_eq!(new_len, 0);
|
||||
|
||||
VecParts {
|
||||
ptr: NonNull::new_unchecked(new_ptr).cast(),
|
||||
len: new_len,
|
||||
cap: new_cap,
|
||||
}
|
||||
let new_parts = VecParts::from_vec(vec);
|
||||
debug_assert!(std::ptr::eq(new_parts.ptr.as_ptr(), parts.ptr.as_ptr()));
|
||||
debug_assert_eq!(new_parts.cap, parts.cap);
|
||||
debug_assert_eq!(new_parts.len, 0);
|
||||
new_parts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,24 +35,19 @@ impl TypeErasedVecVtable {
|
||||
// SAFETY: We reconstruct the Vec to trigger a reserve.
|
||||
// Capacity might change upon reallocation.
|
||||
unsafe {
|
||||
let mut vec =
|
||||
Vec::from_raw_parts(parts.ptr.as_ptr().cast::<T>(), parts.len, parts.cap);
|
||||
let mut vec = parts.into_vec::<T>();
|
||||
vec.reserve(additional);
|
||||
let (new_ptr, new_len, new_cap) = Vec::into_raw_parts(vec);
|
||||
debug_assert_eq!(new_len, parts.len);
|
||||
let new_parts = VecParts::from_vec(vec);
|
||||
debug_assert_eq!(new_parts.len, parts.len);
|
||||
|
||||
VecParts {
|
||||
ptr: NonNull::new_unchecked(new_ptr).cast(),
|
||||
len: new_len,
|
||||
cap: new_cap,
|
||||
}
|
||||
new_parts
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
reserve_vector: reserve_vec::<T>,
|
||||
clear_vector: clear_vec::<T>,
|
||||
drop_vector: drop_vec::<T>,
|
||||
reserve: reserve_vec::<T>,
|
||||
clear: clear_vec::<T>,
|
||||
drop: drop_vec::<T>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user