diff --git a/README.md b/README.md index c3b5bcc..6983eb3 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,14 @@ [![CI](https://github.com/soruh/type_erased_vec/actions/workflows/ci.yml/badge.svg)](https://github.com/soruh/type_erased_vec/actions/workflows/ci.yml) -`type_erased_vec_capacity` retains a [`Vec`] allocation while temporarily -erasing its element type. The allocation can later be reused by another element -type with exactly the same [`Layout`], avoiding an unnecessary deallocation and -allocation cycle. +`type_erased_vec_capacity` keeps a [`Vec`] allocation available while its +element type is temporarily erased. This is useful when a buffer is reused for +different element types with the same [`Layout`], without freeing and +reallocating it between uses. -Safe conversion APIs clear the old elements before changing the element type. -Unsafe retained-element casts preserve the old elements and require identical -size and alignment, valid values of the target type, and valid target-type drop -behavior. - -Scoped guards support borrowed, non-`'static` values whose types do not need -drop. Outside a scoped guard, retained bytes are treated as [`MaybeUninit`] so -expired references are never reconstructed or dropped. `SendTypeErasedVec` and -`SendSyncTypeErasedVec` enforce `Send` and `Send + Sync` bounds, respectively, -on every API that can initialize or reinterpret elements. - -Operations that panic leave the erased owner valid. Layout-mismatch panics -retain their documented behavior. Mutations completed before a panic may be -preserved; the crate does not promise transactional rollback. +The safe APIs clear the current elements before changing the type. A +[`ContentGuard`] provides ordinary `Vec`-like access while the type is fixed; +dropping the guard returns the allocation to its erased owner. ```rust use type_erased_vec_capacity::TypeErasedVec; @@ -35,6 +24,54 @@ assert_eq!(values.as_slice(), &[-1]); assert_eq!(values.capacity(), capacity); ``` +The guard can also be used to fill a buffer, inspect it, and leave it ready for +the next user: + +```rust +use type_erased_vec_capacity::TypeErasedVec; + +let mut buffer = TypeErasedVec::new(Vec::::with_capacity(4)); +{ + let mut values = buffer.as_type::(); + values.with(|vec| vec.extend_from_slice(&[10, 20, 30])); + assert_eq!(values.as_slice(), &[10, 20, 30]); +} + +assert_eq!(buffer.length(), 3); +``` + +For borrowed values, use a scoped guard. The guard cannot outlive the borrowed +data, and its element type must not need a destructor: + +```rust +use type_erased_vec_capacity::TypeErasedVec; + +let mut buffer = TypeErasedVec::new(Vec::<&'static str>::new()); +let message = String::from("reused buffer"); +{ + let mut values = buffer.as_type_scoped::<&str>(); + values.push(&message); + assert_eq!(values.as_slice(), &["reused buffer"]); +} +``` + +When a buffer needs to cross a thread boundary, use the wrapper matching the +element bounds: + +```rust +use type_erased_vec_capacity::SendTypeErasedVec; + +let buffer = SendTypeErasedVec::new(Vec::::new()); +let handle = std::thread::spawn(move || { + let mut buffer = buffer; + let mut values = buffer.as_type::(); + values.push(String::from("work")); + values.take() +}); + +assert_eq!(handle.join().unwrap(), vec!["work"]); +``` + The minimum supported Rust version is documented in the package manifest. ## License diff --git a/src/guard.rs b/src/guard.rs index cdd18a9..4e69251 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -3,9 +3,12 @@ use std::fmt; use std::marker::PhantomData; use std::mem::ManuallyDrop; -/// Provides typed access to a [`TypeErasedVec`] allocation. +/// Provides temporary, typed access to a [`TypeErasedVec`] allocation. /// -/// Dropping the guard retains its initialized elements in the erased vector. +/// Use the guard like a `Vec`: inspect it with [`ContentGuard::as_slice`], add +/// values with [`ContentGuard::push`], or make several changes with +/// [`ContentGuard::with`]. Dropping the guard returns the current elements and +/// allocation to the erased vector. pub struct ContentGuard<'vec, T> { erased: &'vec mut TypeErasedVec, reerase: fn(Vec) -> TypeErasedVec, @@ -59,7 +62,7 @@ impl<'vec, T> ContentGuard<'vec, T> { } } - /// Removes all elements and the allocation from the guard. + /// Takes the typed `Vec` out of the guard. /// /// The guard remains usable with an empty, zero-capacity vector. pub fn take(&mut self) -> Vec { @@ -75,7 +78,7 @@ impl<'vec, T> ContentGuard<'vec, T> { unsafe { parts.into_vec() } } - /// Calls `function` with the underlying vector and then type-erases it again. + /// Runs `function` with the underlying `Vec` and then erases it again. /// /// Mutations made by `function` are retained on normal return. /// @@ -93,7 +96,7 @@ impl<'vec, T> ContentGuard<'vec, T> { f(restore_guard.vec_mut()) } - /// Removes and drops all initialized elements. + /// Removes and drops all elements. pub fn clear(&mut self) { self.with(Vec::clear); } @@ -103,7 +106,7 @@ impl<'vec, T> ContentGuard<'vec, T> { self.with(|vec| vec.reserve(additional)); } - /// Returns the allocation's capacity in elements. + /// Returns the vector's capacity in elements. #[must_use] pub const fn capacity(&self) -> usize { self.erased.capacity() @@ -114,13 +117,13 @@ impl<'vec, T> ContentGuard<'vec, T> { self.with(|vec| vec.push(value)); } - /// Returns the number of initialized elements. + /// Returns the number of elements currently stored. #[must_use] pub const fn length(&self) -> usize { self.erased.length() } - /// Returns `true` when there are no initialized elements. + /// Returns `true` when the vector contains no elements. #[must_use] pub const fn is_empty(&self) -> bool { self.erased.is_empty() diff --git a/src/lib.rs b/src/lib.rs index 4d16d8d..e38a6c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,35 @@ use vtable::TypeErasedVecVtable; use crate::parts::VecParts; -/// Stores the capacity of a `Vec` for later reuse as a `Vec` where `V` shares the same `Layout` as `U`. +/// Keeps a `Vec` allocation available while temporarily erasing its element type. /// -/// Types whose destructors may be retained must be `'static`. Use -/// [`TypeErasedVec::as_type_scoped`] for non-`'static` types that do not need -/// drop, such as borrowed references. +/// Safe type changes clear the current elements first. Use a [`ContentGuard`] to +/// work with the allocation as an ordinary `Vec` while its type is fixed; when +/// the guard is dropped, the allocation returns to this erased owner. +/// +/// The allocation can be reused for another type only when both types have the +/// same [`Layout`]. This makes it useful for buffers that alternate between +/// compatible representations without repeatedly allocating. +/// +/// ``` +/// use type_erased_vec_capacity::TypeErasedVec; +/// +/// let mut buffer = TypeErasedVec::new(Vec::::with_capacity(3)); +/// let initial_capacity = buffer.capacity(); +/// { +/// let mut values = buffer.as_type::(); +/// values.with(|vec| vec.extend_from_slice(&[1, 2, 3])); +/// } +/// { +/// let mut values = buffer.as_type::(); +/// values.push(-1); +/// assert_eq!(values.as_slice(), &[-1]); +/// } +/// assert_eq!(buffer.capacity(), initial_capacity); +/// ``` +/// +/// Use [`TypeErasedVec::as_type_scoped`] for borrowed, non-`'static` values +/// whose type does not need a destructor. /// /// ```compile_fail /// use type_erased_vec_capacity::TypeErasedVec; @@ -40,8 +64,7 @@ use crate::parts::VecParts; /// } /// ``` pub struct TypeErasedVec { - /// The Layout the capacity was allocated with. We need this to confirm that - /// any future conversion back to a `Vec` use the correct `Layout`. + /// Layout reserved for the allocation. layout: Layout, /// Raw parts of the erased vector @@ -63,9 +86,9 @@ impl fmt::Debug for TypeErasedVec { } impl TypeErasedVec { - /// Type erase the underlying Capacity of a `Vec` remembering the `Layout` it was allocated with. - /// Elements inside the `Vec` are retained. - /// Conversion back to a `Vec` is only allowed for types with the same Layout. + /// Erases the element type while retaining the vector's elements and allocation. + /// + /// The elements can later be accessed as a type with the same [`Layout`]. #[must_use] pub fn new(vec: Vec) -> Self { Self { @@ -90,7 +113,7 @@ impl TypeErasedVec { } } - /// Clear any remaining elements in the vector, dropping them. + /// Removes and drops all elements while retaining the allocation. /// /// # Panics /// @@ -101,31 +124,31 @@ impl TypeErasedVec { unsafe { (self.vtable.clear)(&mut self.parts) }; } - /// Reserve additional elements in the vector. + /// Reserves space for at least `additional` more elements. pub fn reserve(&mut self, additional: usize) { // SAFETY: The reserve function correctly targets the currently stored type elements. unsafe { (self.vtable.reserve)(&mut self.parts, additional) }; } - /// Returns the element layout associated with the allocation. + /// Returns the [`Layout`] reserved for the allocation. #[must_use] pub const fn layout(&self) -> Layout { self.layout } - /// Returns the allocation's capacity in elements. + /// Returns the allocation's capacity, measured in elements of its current type. #[must_use] pub const fn capacity(&self) -> usize { self.parts.capacity() } - /// Returns the number of initialized elements. + /// Returns the number of elements currently stored. #[must_use] pub const fn length(&self) -> usize { self.parts.length() } - /// Returns `true` when there are no initialized elements. + /// Returns `true` when the vector contains no elements. #[must_use] pub const fn is_empty(&self) -> bool { self.parts.length() == 0 @@ -144,8 +167,7 @@ impl TypeErasedVec { unsafe { capacity_bytes.unwrap_unchecked() } } - /// Access the erased capacity with a temporarily fixed type. - /// Casts the present elements to the new type without calling their destructor. + /// Reinterprets the current elements as `T` without clearing them. /// /// # Safety /// `T` must have exactly the same [`Layout`] as the erased element type. @@ -169,8 +191,7 @@ impl TypeErasedVec { ContentGuard::new(self) } - /// Access the erased capacity with a temporarily fixed type. - /// Clears all elements currently stored in the `TypeErasedVec`. + /// Clears the current elements and accesses the allocation as `T`. /// /// Returns `None` if `T` does not have the same [`Layout`] as the /// underlying capacity. On failure, the erased vector is unchanged. @@ -187,8 +208,7 @@ impl TypeErasedVec { Some(ContentGuard::new(self)) } - /// Access the erased capacity with a temporarily fixed type. - /// Clears all elements currently stored in the `TypeErasedVec`. + /// Clears the current elements and accesses the allocation as `T`. /// /// # Panics /// Panics if `T` does not have the same `Layout` as the underlying @@ -209,12 +229,12 @@ impl TypeErasedVec { }) } - /// Access the erased capacity using a possibly non-`'static` type. + /// Clears the current elements and accesses the allocation as a possibly + /// non-`'static` type. /// - /// 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. + /// The guard is tied to the lifetime of `T`, so it can safely contain + /// borrowed values. A layout mismatch returns `None` without changing the + /// erased vector. /// /// # Panics /// Panics if `T` needs drop. @@ -233,11 +253,11 @@ impl TypeErasedVec { Some(ContentGuard::new_scoped(self)) } - /// Access the erased capacity using a possibly non-`'static` type. + /// Clears the current elements and accesses the allocation as a possibly + /// non-`'static` type. /// - /// 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. + /// The guard is tied to the lifetime of `T`, so it can safely contain + /// borrowed values. /// /// # Panics /// Panics if `T` needs drop or does not have the erased allocation's layout. @@ -258,8 +278,8 @@ impl TypeErasedVec { }) } - /// Access the erased capacity with a temporarily fixed type. - /// Clears all elements currently stored in the `TypeErasedVec`. + /// Clears the current elements and accesses the allocation as `T` without a + /// release-mode layout check. /// /// # Safety /// `T` must have the same `Layout` as the underlying capacity. @@ -279,7 +299,7 @@ impl TypeErasedVec { unsafe { res.unwrap_unchecked() } } - /// Convert the capacity of the erased `Vec` into a `Vec`. + /// Converts the erased vector into a `Vec` when the layouts match. /// /// # Errors /// Returns the original `TypeErasedVec` if `T` does not have the same `Layout`. @@ -291,7 +311,7 @@ impl TypeErasedVec { } } - /// Convert the capacity of the erased `Vec` into a `Vec`. + /// Clears the current elements and converts the allocation into a `Vec`. /// /// # Panics /// Panics if `T` does not have the same `Layout` as the underlying capacity. @@ -300,7 +320,7 @@ impl TypeErasedVec { self.as_type::().take() } - /// Convert the capacity of the erased `Vec` into a `Vec`. + /// Converts the erased vector into a `Vec` without clearing its elements. /// /// # Safety /// `T` must have exactly the same [`Layout`] as the erased element type. @@ -312,7 +332,8 @@ impl TypeErasedVec { unsafe { self.cast_type::() }.take() } - /// Convert the capacity of the erased `Vec` into a `Vec`. + /// Clears the current elements and converts the allocation into a `Vec` + /// without a release-mode layout check. /// /// # Safety /// `T` must have the same `Layout` as the underlying capacity. @@ -322,11 +343,10 @@ impl TypeErasedVec { unsafe { self.as_type_unchecked().take() } } - /// Clear a type-erased vector so it can be safely moved or shared across - /// threads while preserving its allocation. + /// Clears the elements and wraps the allocation for cross-thread use. /// - /// Use [`SendTypeErasedVec::new`] or [`SendSyncTypeErasedVec::new`] before - /// erasing the element type when the elements themselves should be retained. + /// Use [`SendTypeErasedVec::new`] or [`SendSyncTypeErasedVec::new`] instead + /// when the elements themselves need to cross a thread boundary. #[must_use] pub fn send(mut self) -> SendSyncTypeErasedVec { self.clear(); diff --git a/src/send.rs b/src/send.rs index c63d6af..23051a9 100644 --- a/src/send.rs +++ b/src/send.rs @@ -23,7 +23,7 @@ macro_rules! define_thread_safe_erased_vec { } impl $name { - /// Type erase a vector while retaining its elements and allocation. + /// Erases a vector's element type while retaining its elements and allocation. #[must_use] pub fn new(vec: Vec) -> Self where @@ -32,17 +32,17 @@ macro_rules! define_thread_safe_erased_vec { Self(TypeErasedVec::new(vec)) } - /// Clear all elements while preserving the allocation. + /// Removes and drops all elements while retaining the allocation. pub fn clear(&mut self) { self.0.clear(); } - /// Reserve space for at least `additional` more elements. + /// Reserves space for at least `additional` more elements. pub fn reserve(&mut self, additional: usize) { self.0.reserve(additional); } - /// Returns the element layout associated with the allocation. + /// Returns the [`Layout`] reserved for the allocation. #[must_use] pub const fn layout(&self) -> Layout { self.0.layout() @@ -54,13 +54,13 @@ macro_rules! define_thread_safe_erased_vec { self.0.capacity() } - /// Returns the number of initialized elements. + /// Returns the number of elements currently stored. #[must_use] pub const fn length(&self) -> usize { self.0.length() } - /// Returns `true` when there are no initialized elements. + /// Returns `true` when the vector contains no elements. #[must_use] pub const fn is_empty(&self) -> bool { self.0.is_empty() @@ -72,7 +72,7 @@ macro_rules! define_thread_safe_erased_vec { self.0.capacity_bytes() } - /// Access the erased vector as `T` without clearing its elements. + /// Reinterprets the current elements as `T` without clearing them. /// /// # Safety /// `T` must have exactly the same [`Layout`] as the erased element @@ -89,7 +89,7 @@ macro_rules! define_thread_safe_erased_vec { unsafe { self.0.cast_type() } } - /// Clear the vector and access its allocation as a vector of `T`. + /// Clears the current elements and accesses the allocation as `T`. /// /// A layout mismatch returns `None` without changing the erased /// vector. @@ -100,7 +100,7 @@ macro_rules! define_thread_safe_erased_vec { self.0.try_as_type() } - /// Clear the vector and access its allocation as a vector of `T`. + /// Clears the current elements and accesses the allocation as `T`. /// /// # Panics /// Panics if `T` does not have the erased allocation's layout. A @@ -112,7 +112,7 @@ macro_rules! define_thread_safe_erased_vec { self.0.as_type() } - /// Clear the vector and access its allocation using a possibly + /// Clears the current elements and accesses the allocation using a possibly /// non-`'static` type that does not need drop. /// /// A layout mismatch returns `None` without changing the erased @@ -127,7 +127,7 @@ macro_rules! define_thread_safe_erased_vec { self.0.try_as_type_scoped() } - /// Clear the vector and access its allocation using a possibly + /// Clears the current elements and accesses the allocation using a possibly /// non-`'static` type that does not need drop. /// /// # Panics @@ -141,7 +141,7 @@ macro_rules! define_thread_safe_erased_vec { self.0.as_type_scoped() } - /// Clear the vector and access its allocation as a vector of `T` + /// Clears the current elements and accesses the allocation as `T` /// without a release-mode layout check. /// /// # Safety @@ -154,7 +154,7 @@ macro_rules! define_thread_safe_erased_vec { unsafe { self.0.as_type_unchecked() } } - /// Clear the vector and convert its allocation into a `Vec`. + /// Clears the current elements and converts the allocation into a `Vec`. /// /// # Errors /// Returns this erased vector if `T` does not have the erased @@ -166,7 +166,7 @@ macro_rules! define_thread_safe_erased_vec { self.0.try_into_vec().map_err(Self) } - /// Clear the vector and convert its allocation into a `Vec`. + /// Clears the current elements and converts the allocation into a `Vec`. /// /// # Panics /// Panics if `T` does not have the erased allocation's layout. @@ -178,7 +178,7 @@ macro_rules! define_thread_safe_erased_vec { self.0.into_vec() } - /// Convert the erased vector into a `Vec` without clearing it. + /// Converts the erased vector into a `Vec` without clearing it. /// /// # Safety /// `T` must have exactly the same [`Layout`] as the erased element @@ -195,7 +195,7 @@ macro_rules! define_thread_safe_erased_vec { unsafe { self.0.cast_into_vec() } } - /// Clear the vector and convert its allocation into a `Vec` + /// Clears the current elements and converts the allocation into a `Vec` /// without a release-mode layout check. /// /// # Safety @@ -209,7 +209,7 @@ macro_rules! define_thread_safe_erased_vec { unsafe { self.0.into_vec_unchecked() } } - /// Remove the thread-safety restriction. + /// Returns the underlying erased vector. #[must_use] pub fn unpack(self) -> TypeErasedVec { self.0 @@ -219,7 +219,7 @@ macro_rules! define_thread_safe_erased_vec { } define_thread_safe_erased_vec!( - /// A type-erased vector whose initialized element type is always `Send`. + /// A type-erased vector whose elements are always `Send`. /// /// The vector can be moved between threads, but it cannot be shared between /// threads because its elements are not required to be `Sync`. @@ -244,7 +244,7 @@ define_thread_safe_erased_vec!( ); define_thread_safe_erased_vec!( - /// A type-erased vector whose initialized element type is always `Send + Sync`. + /// A type-erased vector whose elements are always `Send + Sync`. /// /// The vector can be moved or shared between threads. ///