From dd9a2508bb2d0516ed77eae0146dc4bd2c50ea4a Mon Sep 17 00:00:00 2001 From: soruh Date: Fri, 31 Jul 2026 04:12:16 +0200 Subject: [PATCH] Encapsulate VecParts invariants --- src/lib.rs | 12 ++++++------ src/parts.rs | 35 ++++++++++++++++++++++++++++++----- src/vtable.rs | 13 +++++++------ 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index df7be45..60f0bbd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,8 +56,8 @@ impl fmt::Debug for TypeErasedVec { formatter .debug_struct("TypeErasedVec") .field("layout", &self.layout) - .field("length", &self.parts.len) - .field("capacity", &self.parts.cap) + .field("length", &self.parts.length()) + .field("capacity", &self.parts.capacity()) .finish_non_exhaustive() } } @@ -113,19 +113,19 @@ impl TypeErasedVec { /// Returns the allocation's capacity in elements. #[must_use] pub const fn capacity(&self) -> usize { - self.parts.cap + self.parts.capacity() } /// Returns the number of initialized elements. #[must_use] pub const fn length(&self) -> usize { - self.parts.len + self.parts.length() } /// Returns `true` when there are no initialized elements. #[must_use] pub const fn is_empty(&self) -> bool { - self.parts.len == 0 + self.parts.length() == 0 } /// Returns the allocation's capacity in bytes. @@ -134,7 +134,7 @@ impl TypeErasedVec { /// logical capacity. #[must_use] pub const fn capacity_bytes(&self) -> usize { - let capacity_bytes = self.parts.cap.checked_mul(self.layout.size()); + let capacity_bytes = self.parts.capacity().checked_mul(self.layout.size()); // SAFETY: A valid Vec allocation cannot exceed usize::MAX bytes. // ZST vectors multiply their logical capacity by zero. diff --git a/src/parts.rs b/src/parts.rs index ece5c50..ab11705 100644 --- a/src/parts.rs +++ b/src/parts.rs @@ -1,10 +1,15 @@ use std::mem::ManuallyDrop; use std::ptr::NonNull; +/// A linear ownership descriptor for a type-erased `Vec` allocation. +/// +/// Its pointer originates from `Vec`'s global allocator, its length covers +/// initialized elements of the erased type, its capacity uses that type's exact +/// layout, and exactly one descriptor owns and may reconstruct the allocation. pub(super) struct VecParts { - pub(super) ptr: NonNull, - pub(super) len: usize, - pub(super) cap: usize, + ptr: NonNull, + len: usize, + cap: usize, } impl VecParts { @@ -22,19 +27,39 @@ impl VecParts { Self { ptr, len, cap } } + pub(super) const fn length(&self) -> usize { + self.len + } + + pub(super) const fn capacity(&self) -> usize { + self.cap + } + + pub(super) const fn pointer(&self) -> NonNull { + self.ptr + } + pub(super) unsafe fn into_vec(self) -> Vec { + // SAFETY: The VecParts invariant guarantees allocator origin, unique + // ownership, initialized length, capacity, and single reconstruction. + // The caller selects T with the erased allocation's exact layout and + // guarantees that every initialized element is valid as T. unsafe { Vec::from_raw_parts(self.ptr.as_ptr().cast::(), self.len, self.cap) } } #[must_use] pub(super) const unsafe fn as_slice(&self) -> &[T] { - // SAFETY: Ensured by the caller + // SAFETY: The VecParts invariant guarantees a live allocation and + // initialized length. The caller selects the exact erased type T and + // guarantees no mutable access overlaps the returned shared borrow. unsafe { std::slice::from_raw_parts(self.ptr.as_ptr().cast(), self.len) } } #[must_use] pub(super) const unsafe fn as_slice_mut(&mut self) -> &mut [T] { - // SAFETY: Ensured by the caller + // SAFETY: The VecParts invariant guarantees a live allocation and + // initialized length. The caller selects the exact erased type T and + // guarantees unique access with no overlapping references. unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr().cast(), self.len) } } } diff --git a/src/vtable.rs b/src/vtable.rs index 9b956e4..49b3c24 100644 --- a/src/vtable.rs +++ b/src/vtable.rs @@ -15,9 +15,10 @@ impl<'parts, T> VecPartsRestoreGuard<'parts, T> { unsafe fn new(parts: &'parts mut VecParts) -> Self { let original_parts = std::mem::replace(parts, VecParts::from_vec(Vec::::new())); - // SAFETY: The vtable selects T as the exact type that originally - // produced these raw parts, and ownership was transferred out of - // `parts` before reconstruction. + // SAFETY: The VecParts invariant guarantees global-allocator origin, + // valid initialized length and capacity, unique ownership, and single + // reconstruction. The vtable selects T with the exact erased layout, + // and replacement transferred ownership out of `parts`. let vec = unsafe { original_parts.into_vec::() }; Self { @@ -73,8 +74,8 @@ impl TypeErasedVecVtable { } unsafe fn clear_vec(parts: &mut VecParts) { - let original_ptr = parts.ptr.as_ptr(); - let original_capacity = parts.cap; + let original_ptr = parts.pointer().as_ptr(); + let original_capacity = parts.capacity(); // SAFETY: This vtable function is selected only for raw parts that // were produced by Vec. @@ -89,7 +90,7 @@ impl TypeErasedVecVtable { } unsafe fn reserve_vec(parts: &mut VecParts, additional: usize) { - let original_len = parts.len; + let original_len = parts.length(); // SAFETY: This vtable function is selected only for raw parts that // were produced by Vec.