initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
target/
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "type_erased_vec_capacity"
|
||||
version = "0.1.0"
|
||||
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "type_erased_vec_capacity"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::alloc::Layout;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
pub struct TypeErasedVec {
|
||||
ptr: NonNull<u8>,
|
||||
len: usize,
|
||||
cap: usize,
|
||||
layout: Layout,
|
||||
// Store a function pointer to the original type's drop logic.
|
||||
// This ensures elements and memory are cleaned up if this struct is dropped.
|
||||
drop_impl: unsafe fn(NonNull<u8>, usize, usize),
|
||||
}
|
||||
|
||||
impl TypeErasedVec {
|
||||
pub fn new<T>(vec: Vec<T>) -> Self {
|
||||
let layout = Layout::new::<T>();
|
||||
|
||||
// Deconstruct the original vector into its raw components.
|
||||
let (ptr, len, cap) = vec.into_raw_parts();
|
||||
|
||||
// Vec guarantees its pointer is never null, even when capacity is zero.
|
||||
let ptr = unsafe { NonNull::new_unchecked(ptr as *mut u8) };
|
||||
|
||||
// Define a cleanup function bound to the original type.
|
||||
unsafe fn drop_vec<T>(ptr: NonNull<u8>, len: usize, cap: usize) {
|
||||
let _ = Vec::from_raw_parts(ptr.as_ptr() as *mut T, len, cap);
|
||||
}
|
||||
|
||||
Self {
|
||||
ptr,
|
||||
len,
|
||||
cap,
|
||||
layout,
|
||||
drop_impl: drop_vec::<T>,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_vec<T>(self) -> Vec<T> {
|
||||
assert_eq!(
|
||||
Layout::new::<T>(),
|
||||
self.layout,
|
||||
"Target type layout must exactly match the erased layout"
|
||||
);
|
||||
|
||||
// Wrap self in ManuallyDrop to bypass our custom Drop implementation.
|
||||
// This transfers ownership of the memory to the new Vec.
|
||||
let md = ManuallyDrop::new(self);
|
||||
|
||||
unsafe { Vec::from_raw_parts(md.ptr.as_ptr() as *mut T, md.len, md.cap) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TypeErasedVec {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.drop_impl)(self.ptr, self.len, self.cap);
|
||||
}
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::TypeErasedVec;
|
||||
|
||||
use super::*;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct TypeA {
|
||||
a: u32,
|
||||
b: u16,
|
||||
c: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct TypeB {
|
||||
x: u64,
|
||||
}
|
||||
|
||||
// A helper to track when values are dropped
|
||||
// This is crucial for verifying that TypeErasedVec does not leak memory or skip destructors
|
||||
#[derive(Debug)]
|
||||
struct DropTracker {
|
||||
counter: Rc<Cell<usize>>,
|
||||
}
|
||||
|
||||
impl Drop for DropTracker {
|
||||
fn drop(&mut self) {
|
||||
self.counter.set(self.counter.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_successful_conversion() {
|
||||
let original = vec![TypeA { a: 1, b: 2, c: 3 }, TypeA { a: 4, b: 5, c: 6 }];
|
||||
|
||||
let erased = TypeErasedVec::new(original);
|
||||
let converted = erased.into_vec::<TypeB>();
|
||||
|
||||
assert_eq!(converted.len(), 2);
|
||||
|
||||
// The exact bit pattern of TypeA {1, 2, 3} depends on endianness
|
||||
// Miri will ensure this read is memory-safe regardless of the values inside
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_capacity_vec() {
|
||||
// Vec::new() does not allocate
|
||||
// The pointer is dangling but non-null
|
||||
let original: Vec<TypeA> = Vec::new();
|
||||
|
||||
let erased = TypeErasedVec::new(original);
|
||||
let converted = erased.into_vec::<TypeB>();
|
||||
|
||||
assert_eq!(converted.capacity(), 0);
|
||||
assert_eq!(converted.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_sized_types() {
|
||||
// ZSTs do not allocate memory but the length must be tracked correctly
|
||||
let original = vec![(), (), ()];
|
||||
|
||||
let erased = TypeErasedVec::new(original);
|
||||
let converted = erased.into_vec::<()>();
|
||||
|
||||
assert_eq!(converted.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_erased_vec_drop_cleans_up_elements() {
|
||||
let drop_count = Rc::new(Cell::new(0));
|
||||
|
||||
let original = vec![
|
||||
DropTracker {
|
||||
counter: Rc::clone(&drop_count),
|
||||
},
|
||||
DropTracker {
|
||||
counter: Rc::clone(&drop_count),
|
||||
},
|
||||
DropTracker {
|
||||
counter: Rc::clone(&drop_count),
|
||||
},
|
||||
];
|
||||
|
||||
let erased = TypeErasedVec::new(original);
|
||||
|
||||
// Dropping the erased container must trigger the original type's drop logic
|
||||
drop(erased);
|
||||
|
||||
assert_eq!(drop_count.get(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_capacity_is_preserved() {
|
||||
let mut original = Vec::with_capacity(42);
|
||||
original.push(TypeA { a: 0, b: 0, c: 0 });
|
||||
|
||||
let erased = TypeErasedVec::new(original);
|
||||
let converted = erased.into_vec::<TypeB>();
|
||||
|
||||
assert_eq!(converted.capacity(), 42);
|
||||
assert_eq!(converted.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Target type layout must exactly match the erased layout")]
|
||||
fn test_panic_on_size_mismatch() {
|
||||
let original = vec![1u32, 2u32];
|
||||
let erased = TypeErasedVec::new(original);
|
||||
|
||||
// u64 has a different size than u32
|
||||
// This must panic to prevent memory corruption and Miri errors
|
||||
let _converted = erased.into_vec::<u64>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Target type layout must exactly match the erased layout")]
|
||||
fn test_panic_on_alignment_mismatch() {
|
||||
#[repr(align(8))]
|
||||
struct Aligned8([u8; 8]);
|
||||
|
||||
#[repr(align(1))]
|
||||
struct Aligned1([u8; 8]);
|
||||
|
||||
let original = vec![Aligned8([0; 8])];
|
||||
let erased = TypeErasedVec::new(original);
|
||||
|
||||
// Both types are 8 bytes but they have different alignments
|
||||
let _converted = erased.into_vec::<Aligned1>();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user