move to no_std + alloc
CI / Stable checks (push) Successful in 32s
CI / Rust 1.85 (push) Successful in 34s
CI / Miri (push) Successful in 48s
CI / Coverage (push) Successful in 36s

This commit is contained in:
2026-08-04 08:23:29 +02:00
parent 284b3dadf8
commit cd5e904ce1
7 changed files with 61 additions and 41 deletions
+4
View File
@@ -42,6 +42,10 @@ clone_on_ref_ptr = "deny"
shadow_reuse = "deny"
shadow_same = "deny"
shadow_unrelated = "deny"
# Push dependencies down the stack to core and alloc where possible
std_instead_of_core = "deny"
std_instead_of_alloc = "deny"
alloc_instead_of_core = "deny"
# The wrappers' private field is deliberately type-erased and therefore cannot
# express Send/Sync structurally. Their audited APIs preserve the element bounds
+7 -5
View File
@@ -1,7 +1,9 @@
use alloc::vec::Vec;
use crate::TypeErasedVec;
use std::fmt;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use core::fmt;
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
/// Provides temporary, typed access to a [`TypeErasedVec`] allocation.
///
@@ -66,12 +68,12 @@ impl<'vec, T> ContentGuard<'vec, T> {
///
/// The guard remains usable with an empty, zero-capacity vector.
pub fn take(&mut self) -> Vec<T> {
let old_erased = std::mem::replace(self.erased, (self.reerase)(Vec::new()));
let old_erased = core::mem::replace(self.erased, (self.reerase)(Vec::new()));
let erased_owner = ManuallyDrop::new(old_erased);
// SAFETY: ManuallyDrop prevents the old TypeErasedVec from running its
// destructor. This reads its uniquely owned descriptor exactly once.
let parts = unsafe { std::ptr::read(&raw const erased_owner.parts) };
let parts = unsafe { core::ptr::read(&raw const erased_owner.parts) };
// SAFETY: The erased pointer, length, and capacity are valid for Vec<T>,
// and `parts` uniquely owns the allocation.
+15 -9
View File
@@ -1,4 +1,9 @@
#![doc = include_str!("../README.md")]
//
// We only use core and `alloc::vec::Vec` so we are no_std + alloc compatible
// except for in tests where we need to spawn threads to confirm the sync handling
#![cfg_attr(not(test), no_std)]
extern crate alloc;
#[cfg(test)]
mod tests;
@@ -8,11 +13,12 @@ mod parts;
mod send;
mod vtable;
use alloc::vec::Vec;
pub use guard::ContentGuard;
pub use send::{SendSyncTypeErasedVec, SendTypeErasedVec, SendableTypeErasedVec};
use std::alloc::Layout;
use std::fmt;
use core::alloc::Layout;
use core::fmt;
use vtable::TypeErasedVecVtable;
use crate::parts::VecParts;
@@ -100,7 +106,7 @@ impl TypeErasedVec {
fn new_scoped<T>(vec: Vec<T>) -> Self {
debug_assert!(
!std::mem::needs_drop::<T>(),
!core::mem::needs_drop::<T>(),
"scoped element types must not require drop"
);
@@ -109,7 +115,7 @@ impl TypeErasedVec {
layout: Layout::new::<T>(),
// Treat the elements as possibly invalid bytes whenever they are
// outside a ContentGuard carrying their actual lifetime.
vtable: TypeErasedVecVtable::new::<std::mem::MaybeUninit<T>>(),
vtable: TypeErasedVecVtable::new::<core::mem::MaybeUninit<T>>(),
}
}
@@ -223,7 +229,7 @@ impl TypeErasedVec {
panic!(
"Target type layout must exactly match the erased layout. Capacity is reserved for {:?} but {} has {:?}",
layout,
std::any::type_name::<T>(),
core::any::type_name::<T>(),
Layout::new::<T>()
)
})
@@ -240,7 +246,7 @@ impl TypeErasedVec {
/// Panics if `T` needs drop.
pub fn try_as_type_scoped<T>(&mut self) -> Option<ContentGuard<'_, T>> {
assert!(
!std::mem::needs_drop::<T>(),
!core::mem::needs_drop::<T>(),
"scoped element types must not require drop"
);
@@ -249,7 +255,7 @@ impl TypeErasedVec {
}
self.clear();
self.vtable = TypeErasedVecVtable::new::<std::mem::MaybeUninit<T>>();
self.vtable = TypeErasedVecVtable::new::<core::mem::MaybeUninit<T>>();
Some(ContentGuard::new_scoped(self))
}
@@ -272,7 +278,7 @@ impl TypeErasedVec {
panic!(
"Target type layout must exactly match the erased layout. Capacity is reserved for {:?} but {} has {:?}",
layout,
std::any::type_name::<T>(),
core::any::type_name::<T>(),
Layout::new::<T>()
)
})
@@ -291,7 +297,7 @@ impl TypeErasedVec {
res.is_some(),
"Calling `as_type_unchecked` with an incompatible layout is UB! Target type layout must exactly match the erased layout. Capacity is reserved for {:?} but {} has {:?}",
layout,
std::any::type_name::<T>(),
core::any::type_name::<T>(),
Layout::new::<T>()
);
+6 -4
View File
@@ -1,5 +1,7 @@
use std::mem::ManuallyDrop;
use std::ptr::NonNull;
use core::mem::ManuallyDrop;
use core::ptr::NonNull;
use alloc::vec::Vec;
/// A linear ownership descriptor for a type-erased `Vec` allocation.
///
@@ -52,7 +54,7 @@ impl VecParts {
// 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) }
unsafe { core::slice::from_raw_parts(self.ptr.as_ptr().cast(), self.len) }
}
#[must_use]
@@ -60,6 +62,6 @@ impl VecParts {
// 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) }
unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr().cast(), self.len) }
}
}
+3 -2
View File
@@ -1,6 +1,7 @@
use crate::{ContentGuard, TypeErasedVec};
use std::alloc::Layout;
use std::fmt;
use alloc::vec::Vec;
use core::alloc::Layout;
use core::fmt;
macro_rules! define_thread_safe_erased_vec {
(
+20 -17
View File
@@ -3,15 +3,18 @@
reason = "panic paths and panic assertions are intentional test behavior"
)]
use std::panic::catch_unwind;
use alloc::rc::Rc;
use alloc::sync::Arc;
use alloc::{format, vec};
use super::*;
use std::alloc::Layout;
use std::cell::Cell;
use std::marker::PhantomData;
use std::mem::{align_of, size_of};
use std::panic;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use core::cell::Cell;
use core::marker::PhantomData;
use core::mem::{align_of, size_of};
use core::panic;
use core::sync::atomic::{AtomicUsize, Ordering};
struct DropTracker {
counter: Rc<Cell<usize>>,
@@ -172,14 +175,14 @@ macro_rules! assert_thread_safe_erased_vec_api {
assert!(erased.try_as_type::<u8>().is_none());
assert!(
panic::catch_unwind(panic::AssertUnwindSafe(|| {
catch_unwind(panic::AssertUnwindSafe(|| {
let _ = erased.as_type::<u8>();
}))
.is_err()
);
assert!(erased.try_as_type_scoped::<u8>().is_none());
assert!(
panic::catch_unwind(panic::AssertUnwindSafe(|| {
catch_unwind(panic::AssertUnwindSafe(|| {
let _ = erased.as_type_scoped::<u8>();
}))
.is_err()
@@ -310,7 +313,7 @@ fn test_reserve_is_unwind_safe() {
let original_capacity = values.capacity();
let mut erased = TypeErasedVec::new(values);
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let result = catch_unwind(panic::AssertUnwindSafe(|| {
erased.reserve(usize::MAX);
}));
@@ -369,7 +372,7 @@ fn test_clear_is_unwind_safe_when_element_drop_panics() {
let original_capacity = values.capacity();
let mut erased = TypeErasedVec::new(values);
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let result = catch_unwind(panic::AssertUnwindSafe(|| {
erased.clear();
}));
@@ -478,7 +481,7 @@ fn test_as_type_mismatch_panics_before_mutation() {
let original_capacity = values.capacity();
let mut erased = TypeErasedVec::new(values);
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let result = catch_unwind(panic::AssertUnwindSafe(|| {
let _ = erased.as_type::<u8>();
}));
@@ -508,7 +511,7 @@ fn test_guard_with_unwind_safety() {
let mut erased = TypeErasedVec::new(vec);
assert_erased_state(&erased, 1, Layout::new::<DropTracker>());
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let res = catch_unwind(panic::AssertUnwindSafe(|| {
let mut guard = unsafe { erased.cast_type::<DropTracker>() };
guard.with(|v| {
@@ -904,7 +907,7 @@ fn test_scoped_guard_restores_after_panic_and_clears_stale_bytes() {
retained_ptr = guard.as_slice().as_ptr();
retained_capacity = guard.capacity();
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let result = catch_unwind(panic::AssertUnwindSafe(|| {
guard.with(|values| {
values.push(&first);
values.push(&second);
@@ -1000,7 +1003,7 @@ fn test_cast_and_clear() {
fn test_scoped_type_rejects_types_that_need_drop() {
let mut erased = TypeErasedVec::new(Vec::<String>::new());
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let result = catch_unwind(panic::AssertUnwindSafe(|| {
let _ = erased.as_type_scoped::<String>();
}));
@@ -1050,7 +1053,7 @@ fn test_as_type_scoped_mismatch_panics_before_mutation() {
let original_capacity = values.capacity();
let mut erased = TypeErasedVec::new(values);
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let result = catch_unwind(panic::AssertUnwindSafe(|| {
let _ = erased.as_type_scoped::<u8>();
}));
+6 -4
View File
@@ -1,5 +1,7 @@
use alloc::vec::Vec;
use crate::VecParts;
use std::mem::ManuallyDrop;
use core::mem::ManuallyDrop;
/// Temporarily reconstructs a typed vector from erased raw parts and writes its
/// latest raw parts back when dropped.
@@ -13,7 +15,7 @@ struct VecPartsRestoreGuard<'parts, T> {
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::<T>::new()));
let original_parts = core::mem::replace(parts, VecParts::from_vec(Vec::<T>::new()));
// SAFETY: The VecParts invariant guarantees global-allocator origin,
// valid initialized length and capacity, unique ownership, and single
@@ -65,7 +67,7 @@ pub(super) struct TypeErasedVecVtable {
impl TypeErasedVecVtable {
pub(super) fn new<T>() -> Self {
unsafe fn drop_vec<T>(parts: &mut VecParts) {
let original_parts = std::mem::replace(parts, VecParts::from_vec(Vec::<T>::new()));
let original_parts = core::mem::replace(parts, VecParts::from_vec(Vec::<T>::new()));
// SAFETY: The vtable selects T as the exact type that originally
// produced these uniquely owned raw parts. Reconstructing the Vec
@@ -81,7 +83,7 @@ impl TypeErasedVecVtable {
// were produced by Vec<T>.
let mut restore_guard = unsafe { VecPartsRestoreGuard::<T>::new(parts) };
restore_guard.vec_mut().clear();
debug_assert!(std::ptr::eq(
debug_assert!(core::ptr::eq(
restore_guard.pointer().cast::<u8>(),
original_ptr
));