diff --git a/src/guard.rs b/src/guard.rs index 1b5879f..282a663 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -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() } } } diff --git a/src/lib.rs b/src/lib.rs index bbf429e..6de0532 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, - pub len: usize, - pub cap: usize, -} - -impl VecParts { - pub(crate) fn from_vec(vec: Vec) -> 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::()) }; - - Self { ptr, len, cap } - } - - pub(crate) unsafe fn into_vec(self) -> Vec { - unsafe { Vec::from_raw_parts(self.ptr.as_ptr().cast::(), self.len, self.cap) } - } -} +use crate::parts::VecParts; /// Stores the capacity of a `Vec` for later reuse as a `Vec` where `V` shares the same `Layout` as `U`. pub struct TypeErasedVec { diff --git a/src/parts.rs b/src/parts.rs new file mode 100644 index 0000000..e23ef7f --- /dev/null +++ b/src/parts.rs @@ -0,0 +1,35 @@ +use std::ptr::NonNull; + +#[derive(Clone, Copy)] +pub(crate) struct VecParts { + pub ptr: NonNull, + pub len: usize, + pub cap: usize, +} + +impl VecParts { + pub(crate) fn from_vec(vec: Vec) -> 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::()) }; + + Self { ptr, len, cap } + } + + pub(crate) unsafe fn into_vec(self) -> Vec { + unsafe { Vec::from_raw_parts(self.ptr.as_ptr().cast::(), self.len, self.cap) } + } + + #[must_use] + pub(crate) unsafe fn as_slice(&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(&mut self) -> &mut [T] { + // SAFETY: Ensured by the caller + unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr().cast(), self.len) } + } +} diff --git a/src/send.rs b/src/send.rs index 923ec2a..c0b6783 100644 --- a/src/send.rs +++ b/src/send.rs @@ -10,6 +10,7 @@ impl SendableTypeErasedVec { Self(vec) } + /// Unpackage the inner `TypeErasedVec`. #[must_use] pub fn unpack(self) -> TypeErasedVec { self.0 diff --git a/src/tests.rs b/src/tests.rs index f240a46..f7500f2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -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( - guard: &ContentGuard<'_, T>, - expected_len: usize, - expected_min_cap: usize, -) { +fn assert_guard_state(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::()); + assert_erased_state(&erased, 5, Layout::new::()); + 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::()); + assert_erased_state(&erased, 5, Layout::new::()); + assert!(erased.capacity() >= initial_cap); assert_eq!(drop_count.get(), 0); erased.clear(); - assert_erased_state(&erased, 0, 5, Layout::new::()); + assert_erased_state(&erased, 0, Layout::new::()); + assert!(erased.capacity() >= initial_cap); assert_eq!(drop_count.get(), 5); } #[test] fn test_reserve_increases_capacity() { - let vec = Vec::::with_capacity(2); + let vec = Vec::::new(); let mut erased = TypeErasedVec::new(vec); - assert_erased_state(&erased, 0, 2, Layout::new::()); + assert_erased_state(&erased, 0, Layout::new::()); erased.reserve(100); - assert_erased_state(&erased, 0, 100, Layout::new::()); + assert_erased_state(&erased, 0, Layout::new::()); + // 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::::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::()); + assert_erased_state(&erased, 2, Layout::new::()); + assert!(erased.capacity() >= initial_cap); - let mut guard = unsafe { erased.try_cast_type::().expect("Layouts match") }; - assert_guard_state(&guard, 2, initial_cap); + let mut guard = unsafe { erased.cast_type::() }; + + 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::::with_capacity(5); - let initial_cap = vec.capacity(); - let mut erased = TypeErasedVec::new(vec); - - assert_erased_state(&erased, 0, initial_cap, Layout::new::()); - - let guard = unsafe { erased.try_cast_type::() }; - assert!(guard.is_none(), "Should fail due to layout mismatch"); - assert_erased_state(&erased, 0, initial_cap, Layout::new::()); + 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::()); + assert_erased_state(&erased, 3, Layout::new::()); - let guard = erased.try_to_type::().expect("Layouts match"); + let Some(guard) = erased.try_to_type::() 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::()); + assert_erased_state(&erased, 1, Layout::new::()); let res = panic::catch_unwind(panic::AssertUnwindSafe(|| { - let mut guard = unsafe { erased.try_cast_type::().unwrap() }; + let mut guard = unsafe { erased.cast_type::() }; + 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::()); + assert_erased_state(&erased, 0, Layout::new::()); 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::()); + assert_erased_state(&erased, 1, Layout::new::()); let Ok(restored) = erased.try_into_vec::() 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::()); + assert_erased_state(&recovered_erased, 0, Layout::new::()); + assert!(recovered_erased.capacity() >= initial_cap); let Ok(restored) = recovered_erased.try_into_vec::() 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::()); + assert_erased_state(&erased, 1, Layout::new::()); let sendable = erased.send(); assert_eq!(drop_count.get(), 1); let unpacked = sendable.unpack(); - assert_erased_state(&unpacked, 0, 5, Layout::new::()); + assert_erased_state(&unpacked, 0, Layout::new::()); + 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::()); + assert_erased_state(&erased, 1, Layout::new::()); assert_eq!(drop_count.get(), 0); } diff --git a/src/vtable.rs b/src/vtable.rs index 8a7efeb..a33afe9 100644 --- a/src/vtable.rs +++ b/src/vtable.rs @@ -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