Update docs
CI / Miri (push) Successful in 50s
CI / Coverage (push) Failing after 47s
CI / Stable checks (push) Successful in 42s
CI / Rust 1.85 (push) Successful in 25s

This commit is contained in:
2026-07-31 12:31:30 +02:00
parent ca0fe8c87f
commit 8391d34484
4 changed files with 145 additions and 85 deletions
+55 -18
View File
@@ -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::<u32>::with_capacity(4));
{
let mut values = buffer.as_type::<u32>();
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::<String>::new());
let handle = std::thread::spawn(move || {
let mut buffer = buffer;
let mut values = buffer.as_type::<String>();
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
+11 -8
View File
@@ -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<T>) -> 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<T> {
@@ -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()
+60 -40
View File
@@ -17,11 +17,35 @@ use vtable::TypeErasedVecVtable;
use crate::parts::VecParts;
/// Stores the capacity of a `Vec<U>` for later reuse as a `Vec<V>` 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::<u32>::with_capacity(3));
/// let initial_capacity = buffer.capacity();
/// {
/// let mut values = buffer.as_type::<u32>();
/// values.with(|vec| vec.extend_from_slice(&[1, 2, 3]));
/// }
/// {
/// let mut values = buffer.as_type::<i32>();
/// 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<T: 'static>(vec: Vec<T>) -> 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<T>` 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<T>` 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<T>`.
/// Converts the erased vector into a `Vec<T>` 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<T>`.
/// Clears the current elements and converts the allocation into a `Vec<T>`.
///
/// # Panics
/// Panics if `T` does not have the same `Layout` as the underlying capacity.
@@ -300,7 +320,7 @@ impl TypeErasedVec {
self.as_type::<T>().take()
}
/// Convert the capacity of the erased `Vec` into a `Vec<T>`.
/// Converts the erased vector into a `Vec<T>` 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::<T>() }.take()
}
/// Convert the capacity of the erased `Vec` into a `Vec<T>`.
/// Clears the current elements and converts the allocation into a `Vec<T>`
/// 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();
+19 -19
View File
@@ -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<T>(vec: Vec<T>) -> 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<T>`.
/// Clears the current elements and converts the allocation into a `Vec<T>`.
///
/// # 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<T>`.
/// Clears the current elements and converts the allocation into a `Vec<T>`.
///
/// # 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<T>` without clearing it.
/// Converts the erased vector into a `Vec<T>` 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<T>`
/// Clears the current elements and converts the allocation into a `Vec<T>`
/// 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.
///