use std::{ffi::c_void, fmt::Display, ptr::NonNull};
use crate::ffi::abort_on_panic;
#[repr(C)]
#[derive(Debug)]
pub struct CVec {
pub ptr: *mut c_void,
pub len: usize,
pub cap: usize,
}
impl CVec {
#[must_use]
pub fn empty() -> Self {
Self {
ptr: NonNull::<u8>::dangling().as_ptr().cast::<c_void>(),
len: 0,
cap: 0,
}
}
#[must_use]
pub unsafe fn into_vec<T>(self) -> Vec<T> {
assert!(
self.len <= self.cap,
"CVec::into_vec: len ({}) > cap ({})",
self.len,
self.cap
);
if self.cap == 0 {
assert_eq!(
self.len, 0,
"CVec::into_vec: zero capacity with non-zero len ({})",
self.len
);
return Vec::new();
}
assert!(
!self.ptr.is_null(),
"CVec::into_vec: null ptr with non-zero cap ({})",
self.cap
);
debug_assert!(self.ptr.cast::<T>().is_aligned());
debug_assert!(
self.cap
.checked_mul(std::mem::size_of::<T>())
.is_some_and(|bytes| isize::try_from(bytes).is_ok())
);
unsafe { Vec::from_raw_parts(self.ptr.cast::<T>(), self.len, self.cap) }
}
#[must_use]
pub unsafe fn as_slice<T>(&self) -> &[T] {
assert!(
self.len <= self.cap,
"CVec::as_slice: len ({}) > cap ({})",
self.len,
self.cap
);
if self.len == 0 {
return &[];
}
assert!(
!self.ptr.is_null(),
"CVec::as_slice: null ptr with non-zero len ({})",
self.len
);
debug_assert!(self.ptr.cast::<T>().is_aligned());
debug_assert!(
self.len
.checked_mul(std::mem::size_of::<T>())
.is_some_and(|bytes| isize::try_from(bytes).is_ok())
);
unsafe { std::slice::from_raw_parts(self.ptr.cast::<T>(), self.len) }
}
}
impl<T> From<Vec<T>> for CVec {
fn from(mut data: Vec<T>) -> Self {
if data.is_empty() {
Self::empty()
} else {
let len = data.len();
let cap = data.capacity();
let ptr = data.as_mut_ptr();
#[allow(
clippy::mem_forget,
reason = "intentional ownership transfer to C; matching CVec::drop reclaims via Vec::from_raw_parts"
)]
std::mem::forget(data);
Self {
ptr: ptr.cast::<std::ffi::c_void>(),
len,
cap,
}
}
}
}
impl Display for CVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"CVec {{ ptr: {:?}, len: {}, cap: {} }}",
self.ptr, self.len, self.cap,
)
}
}
#[cfg(feature = "ffi")]
#[unsafe(no_mangle)]
pub extern "C" fn cvec_new() -> CVec {
abort_on_panic(CVec::empty)
}
#[cfg(test)]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use rstest::*;
use super::CVec;
#[rstest]
#[allow(unused_assignments)]
fn access_values_test() {
let test_data = vec![1_u64, 2, 3];
let mut vec_len = 0;
let mut vec_cap = 0;
let cvec: CVec = {
let data = test_data.clone();
vec_len = data.len();
vec_cap = data.capacity();
data.into()
};
assert_eq!(cvec.len, vec_len);
assert_eq!(cvec.cap, vec_cap);
let data = unsafe { cvec.into_vec::<u64>() };
assert_eq!(data, test_data);
}
#[rstest]
fn empty_vec_should_give_dangling_ptr() {
let data: Vec<u64> = vec![];
let cvec: CVec = data.into();
assert!(!cvec.ptr.is_null());
assert_eq!(cvec.len, 0);
assert_eq!(cvec.cap, 0);
}
#[repr(align(64))]
struct Aligned;
#[rstest]
#[case(CVec::empty())]
#[case(Vec::<u64>::new().into())]
fn empty_into_vec_does_not_inspect_pointer(#[case] cvec: CVec) {
let values = unsafe { cvec.into_vec::<u64>() };
assert!(values.is_empty());
}
#[rstest]
fn aligned_empty_into_vec_does_not_reconstruct_pointer() {
let values = unsafe { CVec::empty().into_vec::<Aligned>() };
assert!(values.is_empty());
}
#[rstest]
fn aligned_empty_as_slice_does_not_inspect_pointer() {
let cvec = CVec::empty();
let values = unsafe { cvec.as_slice::<Aligned>() };
assert!(values.is_empty());
}
#[rstest]
fn non_empty_into_vec_round_trips_and_drops_once() {
struct DropCounter(Arc<AtomicUsize>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let drops = Arc::new(AtomicUsize::new(0));
let cvec: CVec = vec![DropCounter(Arc::clone(&drops))].into();
let values = unsafe { cvec.into_vec::<DropCounter>() };
assert_eq!(drops.load(Ordering::SeqCst), 0);
drop(values);
assert_eq!(drops.load(Ordering::SeqCst), 1);
}
#[rstest]
fn as_slice_borrows_without_consuming_caller_storage() {
let values = vec![1_u64, 2, 3];
let cvec = CVec {
ptr: values.as_ptr().cast_mut().cast(),
len: values.len(),
cap: values.capacity(),
};
let borrowed = unsafe { cvec.as_slice::<u64>() };
assert_eq!(borrowed, values);
assert_eq!(values, [1, 2, 3]);
}
}