diff --git a/src/lib.rs b/src/lib.rs index 3ee216f..e32d8d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,19 +157,27 @@ impl TypeErasedVec { /// Access the erased capacity with a temporarily fixed type. /// Clears all elements currently stored in the `TypeErasedVec`. /// - /// Will fail if `T` does not have the same `Layout` as the underlying capacity. + /// Returns `None` if `T` does not have the same [`Layout`] as the + /// underlying capacity. On failure, the erased vector is unchanged. pub fn try_as_type(&mut self) -> Option> { + if self.layout != Layout::new::() { + return None; + } + self.clear(); + self.vtable = TypeErasedVecVtable::new::(); + // SAFETY: The vector has been cleared, meaning there are no existing elements // that could be invalidated or improperly dropped by the cast. - (self.layout == Layout::new::()).then(|| ContentGuard::new(self)) + Some(ContentGuard::new(self)) } /// 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. + /// Panics if `T` does not have the same `Layout` as the underlying + /// capacity. 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 f84811c..394184f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -363,6 +363,58 @@ fn test_try_as_type_clears_elements() { assert_eq!(drop_count.get(), 3); } +#[test] +fn test_try_as_type_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::().is_none()); + assert_erased_state(&erased, 1, Layout::new::()); + assert_eq!(erased.capacity(), original_capacity); + assert_eq!(drop_count.get(), 0); + + { + // SAFETY: The failed 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_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::(); + })); + + 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 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_guard_with_unwind_safety() { let drop_count = Rc::new(Cell::new(0));