From cd5e904ce1d3f2654c01be96fa2aa10a9a40ab39 Mon Sep 17 00:00:00 2001 From: soruh Date: Tue, 4 Aug 2026 08:23:29 +0200 Subject: [PATCH] move to no_std + alloc --- Cargo.toml | 4 ++++ src/guard.rs | 12 +++++++----- src/lib.rs | 24 +++++++++++++++--------- src/parts.rs | 10 ++++++---- src/send.rs | 5 +++-- src/tests.rs | 37 ++++++++++++++++++++----------------- src/vtable.rs | 10 ++++++---- 7 files changed, 61 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cdef8dd..d6128d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/src/guard.rs b/src/guard.rs index 4e69251..cf459e9 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -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 { - 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, // and `parts` uniquely owns the allocation. diff --git a/src/lib.rs b/src/lib.rs index e38a6c6..1d2007c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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(vec: Vec) -> Self { debug_assert!( - !std::mem::needs_drop::(), + !core::mem::needs_drop::(), "scoped element types must not require drop" ); @@ -109,7 +115,7 @@ impl TypeErasedVec { layout: Layout::new::(), // Treat the elements as possibly invalid bytes whenever they are // outside a ContentGuard carrying their actual lifetime. - vtable: TypeErasedVecVtable::new::>(), + vtable: TypeErasedVecVtable::new::>(), } } @@ -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::(), + core::any::type_name::(), Layout::new::() ) }) @@ -240,7 +246,7 @@ impl TypeErasedVec { /// Panics if `T` needs drop. pub fn try_as_type_scoped(&mut self) -> Option> { assert!( - !std::mem::needs_drop::(), + !core::mem::needs_drop::(), "scoped element types must not require drop" ); @@ -249,7 +255,7 @@ impl TypeErasedVec { } self.clear(); - self.vtable = TypeErasedVecVtable::new::>(); + self.vtable = TypeErasedVecVtable::new::>(); 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::(), + core::any::type_name::(), Layout::new::() ) }) @@ -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::(), + core::any::type_name::(), Layout::new::() ); diff --git a/src/parts.rs b/src/parts.rs index ab11705..c2c6502 100644 --- a/src/parts.rs +++ b/src/parts.rs @@ -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) } } } diff --git a/src/send.rs b/src/send.rs index 23051a9..417749f 100644 --- a/src/send.rs +++ b/src/send.rs @@ -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 { ( diff --git a/src/tests.rs b/src/tests.rs index 48752ba..f8ec915 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -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>, @@ -172,14 +175,14 @@ macro_rules! assert_thread_safe_erased_vec_api { assert!(erased.try_as_type::().is_none()); assert!( - panic::catch_unwind(panic::AssertUnwindSafe(|| { + catch_unwind(panic::AssertUnwindSafe(|| { let _ = erased.as_type::(); })) .is_err() ); assert!(erased.try_as_type_scoped::().is_none()); assert!( - panic::catch_unwind(panic::AssertUnwindSafe(|| { + catch_unwind(panic::AssertUnwindSafe(|| { let _ = erased.as_type_scoped::(); })) .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::(); })); @@ -508,7 +511,7 @@ fn test_guard_with_unwind_safety() { let mut erased = TypeErasedVec::new(vec); assert_erased_state(&erased, 1, Layout::new::()); - let res = panic::catch_unwind(panic::AssertUnwindSafe(|| { + let res = catch_unwind(panic::AssertUnwindSafe(|| { let mut guard = unsafe { erased.cast_type::() }; 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::::new()); - let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { + let result = catch_unwind(panic::AssertUnwindSafe(|| { let _ = erased.as_type_scoped::(); })); @@ -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::(); })); diff --git a/src/vtable.rs b/src/vtable.rs index 49b3c24..948febc 100644 --- a/src/vtable.rs +++ b/src/vtable.rs @@ -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::::new())); + let original_parts = core::mem::replace(parts, VecParts::from_vec(Vec::::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() -> Self { unsafe fn drop_vec(parts: &mut VecParts) { - let original_parts = std::mem::replace(parts, VecParts::from_vec(Vec::::new())); + let original_parts = core::mem::replace(parts, VecParts::from_vec(Vec::::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. let mut restore_guard = unsafe { VecPartsRestoreGuard::::new(parts) }; restore_guard.vec_mut().clear(); - debug_assert!(std::ptr::eq( + debug_assert!(core::ptr::eq( restore_guard.pointer().cast::(), original_ptr ));