continue refactor

This commit is contained in:
2026-07-26 14:33:39 +02:00
parent 4131a127cc
commit eed774b566
6 changed files with 87 additions and 87 deletions
+2 -9
View File
@@ -60,19 +60,12 @@ impl<'vec, T> ContentGuard<'vec, T> {
#[must_use]
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.parts.ptr.as_ptr().cast(), self.erased.parts.len)
}
unsafe { self.erased.parts.as_slice() }
}
#[must_use]
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.parts.ptr.as_ptr().cast(),
self.erased.parts.len,
)
}
unsafe { self.erased.parts.as_slice_mut() }
}
}
+3 -23
View File
@@ -1,9 +1,10 @@
#![warn(clippy::pedantic)]
#![warn(clippy::all, clippy::pedantic)]
#[cfg(test)]
mod tests;
mod guard;
mod parts;
mod send;
mod vtable;
@@ -11,30 +12,9 @@ pub use guard::ContentGuard;
pub use send::SendableTypeErasedVec;
use std::alloc::Layout;
use std::ptr::NonNull;
use vtable::TypeErasedVecVtable;
#[derive(Clone, Copy)]
pub(crate) struct VecParts {
pub ptr: NonNull<u8>,
pub len: usize,
pub cap: usize,
}
impl VecParts {
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.
let ptr = unsafe { NonNull::new_unchecked(ptr.cast::<u8>()) };
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) }
}
}
use crate::parts::VecParts;
/// 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 {
+35
View File
@@ -0,0 +1,35 @@
use std::ptr::NonNull;
#[derive(Clone, Copy)]
pub(crate) struct VecParts {
pub ptr: NonNull<u8>,
pub len: usize,
pub cap: usize,
}
impl VecParts {
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.
let ptr = unsafe { NonNull::new_unchecked(ptr.cast::<u8>()) };
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) }
}
#[must_use]
pub(crate) unsafe fn as_slice<T>(&self) -> &[T] {
// SAFETY: Ensured by the caller
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr().cast(), self.len) }
}
#[must_use]
pub(crate) unsafe fn as_slice_mut<T>(&mut self) -> &mut [T] {
// SAFETY: Ensured by the caller
unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr().cast(), self.len) }
}
}
+1
View File
@@ -10,6 +10,7 @@ impl SendableTypeErasedVec {
Self(vec)
}
/// Unpackage the inner `TypeErasedVec`.
#[must_use]
pub fn unpack(self) -> TypeErasedVec {
self.0
+46 -54
View File
@@ -21,15 +21,9 @@ impl Drop for DropTracker {
}
#[track_caller]
fn assert_erased_state(
erased: &TypeErasedVec,
expected_len: usize,
expected_min_cap: usize,
expected_layout: Layout,
) {
fn assert_erased_state(erased: &TypeErasedVec, expected_len: 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.is_empty(), expected_len == 0, "Is_empty mismatch");
assert_eq!(erased.layout(), expected_layout, "Layout mismatch");
assert_eq!(
erased.capacity_bytes(),
@@ -39,16 +33,8 @@ fn assert_erased_state(
}
#[track_caller]
fn assert_guard_state<T>(
guard: &ContentGuard<'_, T>,
expected_len: usize,
expected_min_cap: usize,
) {
fn assert_guard_state<T>(guard: &ContentGuard<'_, T>, expected_len: 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,
@@ -73,7 +59,8 @@ fn test_new_and_basic_properties() {
let erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 5, initial_cap, Layout::new::<u32>());
assert_erased_state(&erased, 5, Layout::new::<u32>());
assert!(erased.capacity() >= initial_cap);
}
#[test]
@@ -85,63 +72,59 @@ fn test_clear_drops_elements() {
vec.push(DropTracker::new(drop_count.clone()));
}
let initial_cap = vec.capacity();
let mut erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 5, 5, Layout::new::<DropTracker>());
assert_erased_state(&erased, 5, Layout::new::<DropTracker>());
assert!(erased.capacity() >= initial_cap);
assert_eq!(drop_count.get(), 0);
erased.clear();
assert_erased_state(&erased, 0, 5, Layout::new::<DropTracker>());
assert_erased_state(&erased, 0, Layout::new::<DropTracker>());
assert!(erased.capacity() >= initial_cap);
assert_eq!(drop_count.get(), 5);
}
#[test]
fn test_reserve_increases_capacity() {
let vec = Vec::<u64>::with_capacity(2);
let vec = Vec::<u64>::new();
let mut erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 0, 2, Layout::new::<u64>());
assert_erased_state(&erased, 0, Layout::new::<u64>());
erased.reserve(100);
assert_erased_state(&erased, 0, 100, Layout::new::<u64>());
assert_erased_state(&erased, 0, Layout::new::<u64>());
// The standard library guarantees that `reserve` allocates *at least* the requested capacity.
assert!(erased.capacity() >= 100);
}
#[test]
fn test_try_cast_type_success_and_mutation() {
fn test_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>());
assert_erased_state(&erased, 2, Layout::new::<i32>());
assert!(erased.capacity() >= initial_cap);
let mut guard = unsafe { erased.try_cast_type::<u32>().expect("Layouts match") };
assert_guard_state(&guard, 2, initial_cap);
let mut guard = unsafe { erased.cast_type::<u32>() };
assert_guard_state(&guard, 2);
assert!(guard.capacity() >= initial_cap);
let slice = guard.as_slice_mut();
slice[0] = 42;
let slice = guard.as_slice();
assert_eq!(slice, &[42, 4294967294]);
// Casting -2_i32 into u32 results in u32::MAX - 1 due to two's complement.
assert_eq!(slice, &[42, u32::MAX - 1]);
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>());
assert_eq!(restored, vec![42, u32::MAX - 1]);
assert_guard_state(&guard, 0);
}
#[test]
@@ -153,12 +136,16 @@ fn test_try_to_type_clears_elements() {
vec.push(DropTracker::new(drop_count.clone()));
}
let initial_cap = vec.capacity();
let mut erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 3, 5, Layout::new::<DropTracker>());
assert_erased_state(&erased, 3, Layout::new::<DropTracker>());
let guard = erased.try_to_type::<DropTracker>().expect("Layouts match");
let Some(guard) = erased.try_to_type::<DropTracker>() else {
panic!("try_to_type failed despite matching layouts");
};
assert_guard_state(&guard, 0, 5);
assert_guard_state(&guard, 0);
assert!(guard.capacity() >= initial_cap);
assert_eq!(drop_count.get(), 3);
}
@@ -169,10 +156,11 @@ fn test_guard_with_unwind_safety() {
vec.push(DropTracker::new(drop_count.clone()));
let mut erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 1, 10, Layout::new::<DropTracker>());
assert_erased_state(&erased, 1, Layout::new::<DropTracker>());
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let mut guard = unsafe { erased.try_cast_type::<DropTracker>().unwrap() };
let mut guard = unsafe { erased.cast_type::<DropTracker>() };
guard.with(|v| {
v.push(DropTracker::new(drop_count.clone()));
panic!("Simulated panic inside with()");
@@ -181,7 +169,7 @@ fn test_guard_with_unwind_safety() {
assert!(res.is_err());
assert_erased_state(&erased, 0, 0, Layout::new::<DropTracker>());
assert_erased_state(&erased, 0, Layout::new::<DropTracker>());
assert_eq!(drop_count.get(), 2);
}
@@ -192,7 +180,7 @@ fn test_conversion_methods() {
let initial_cap = vec.capacity();
let erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 1, initial_cap, Layout::new::<String>());
assert_erased_state(&erased, 1, Layout::new::<String>());
let Ok(restored) = erased.try_into_vec::<String>() else {
panic!("try_into_vec failed despite matching layouts");
@@ -207,6 +195,7 @@ fn test_conversion_methods() {
let new_cap = guard.capacity();
let restored2 = guard.take();
// Vec::from_raw_parts guarantees the exact capacity reconstructed matches the integer passed in.
assert_eq!(restored2.capacity(), new_cap);
}
@@ -221,7 +210,8 @@ fn test_try_into_vec_failure_and_recovery() {
panic!("try_into_vec succeeded with mismatched layout");
};
assert_erased_state(&recovered_erased, 0, initial_cap, Layout::new::<u32>());
assert_erased_state(&recovered_erased, 0, Layout::new::<u32>());
assert!(recovered_erased.capacity() >= initial_cap);
let Ok(restored) = recovered_erased.try_into_vec::<u32>() else {
panic!("try_into_vec failed after recovery");
@@ -273,15 +263,17 @@ 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()));
let initial_cap = vec.capacity();
let erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 1, 5, Layout::new::<DropTracker>());
assert_erased_state(&erased, 1, 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>());
assert_erased_state(&unpacked, 0, Layout::new::<DropTracker>());
assert!(unpacked.capacity() >= initial_cap);
}
#[test]
@@ -292,7 +284,7 @@ fn test_drop_cleans_up_allocation() {
{
let erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 1, 5, Layout::new::<DropTracker>());
assert_erased_state(&erased, 1, Layout::new::<DropTracker>());
assert_eq!(drop_count.get(), 0);
}
-1
View File
@@ -1,5 +1,4 @@
use crate::VecParts;
use std::ptr::NonNull;
pub(crate) struct TypeErasedVecVtable {
/// Function pointer to reserve additional capacity in the underlying Vec