From 78159657aef8f221de154bbd0bd34daeb303d001 Mon Sep 17 00:00:00 2001 From: soruh Date: Fri, 31 Jul 2026 03:53:27 +0200 Subject: [PATCH] Preserve scoped vectors on type mismatch --- src/lib.rs | 14 ++++++++------ src/tests.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e32d8d3..e085f38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -199,6 +199,7 @@ impl TypeErasedVec { /// The previous elements are cleared. While the guard exists, its lifetime /// tracks `T`. Outside the guard, the allocation is treated as /// `MaybeUninit` so expired references are never reconstructed or dropped. + /// A layout mismatch returns `None` without changing the erased vector. /// /// # Panics /// Panics if `T` needs drop. @@ -207,14 +208,14 @@ impl TypeErasedVec { !std::mem::needs_drop::(), "scoped element types must not require drop" ); - self.clear(); - if self.layout == Layout::new::() { - self.vtable = TypeErasedVecVtable::new::>(); - Some(ContentGuard::new_scoped(self)) - } else { - None + if self.layout != Layout::new::() { + return None; } + + self.clear(); + self.vtable = TypeErasedVecVtable::new::>(); + Some(ContentGuard::new_scoped(self)) } /// Access the erased capacity using a possibly non-`'static` type. @@ -225,6 +226,7 @@ impl TypeErasedVec { /// /// # Panics /// Panics if `T` needs drop or does not have the erased allocation's layout. + /// A layout-mismatch panic leaves the erased vector unchanged. #[expect( clippy::panic, reason = "this infallible convenience method documents and reports layout mismatch" diff --git a/src/tests.rs b/src/tests.rs index 394184f..7ec344a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -705,6 +705,58 @@ fn test_try_as_type_scoped_returns_none_for_mismatched_layout() { assert!(erased_u32.try_as_type_scoped::().is_some()); } +#[test] +fn test_try_as_type_scoped_mismatch_preserves_nonempty_vector() { + let drop_count = Rc::new(Cell::new(0)); + let mut values = Vec::with_capacity(4); + values.push(DropTracker::new(Rc::clone(&drop_count))); + let original_ptr = values.as_ptr(); + let original_capacity = values.capacity(); + let mut erased = TypeErasedVec::new(values); + + assert!(erased.try_as_type_scoped::().is_none()); + assert_erased_state(&erased, 1, Layout::new::()); + assert_eq!(erased.capacity(), original_capacity); + assert_eq!(drop_count.get(), 0); + + { + // SAFETY: The failed scoped probe preserved the original element type. + let guard = unsafe { erased.cast_type::() }; + assert_eq!(guard.as_slice().as_ptr(), original_ptr); + } + + drop(erased); + assert_eq!(drop_count.get(), 1); +} + +#[test] +fn test_as_type_scoped_mismatch_panics_before_mutation() { + let drop_count = Rc::new(Cell::new(0)); + let mut values = Vec::with_capacity(4); + values.push(DropTracker::new(Rc::clone(&drop_count))); + let original_ptr = values.as_ptr(); + let original_capacity = values.capacity(); + let mut erased = TypeErasedVec::new(values); + + let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { + let _ = erased.as_type_scoped::(); + })); + + assert!(result.is_err()); + assert_erased_state(&erased, 1, Layout::new::()); + assert_eq!(erased.capacity(), original_capacity); + assert_eq!(drop_count.get(), 0); + + { + // SAFETY: The panicking scoped probe preserved the original element type. + let guard = unsafe { erased.cast_type::() }; + assert_eq!(guard.as_slice().as_ptr(), original_ptr); + } + + drop(erased); + assert_eq!(drop_count.get(), 1); +} + #[test] #[should_panic(expected = "Target type layout must exactly match")] fn test_as_type_scoped_panics_for_mismatched_layout() {