#![cfg_attr(feature = "allocator_api", feature(allocator_api))]
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(unsafe_op_in_unsafe_fn)]
#![deny(missing_docs)]
extern crate alloc;
use alloc::vec::Vec;
use bytemuck::Pod;
#[cfg(feature = "allocator_api")]
use core::alloc::{AllocError, Allocator, Layout};
use core::{
cmp::Ordering,
fmt::Display,
hint,
marker::PhantomData,
mem::{self, ManuallyDrop},
ptr::{self, NonNull},
slice,
sync::atomic::{self, AtomicPtr},
};
#[cfg(feature = "std")]
use std::error::Error;
#[derive(Debug, PartialEq, Eq)]
pub enum TransmuteError {
Alignment,
Length,
Capacity,
}
impl Display for TransmuteError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
TransmuteError::Alignment => write!(formatter, "Alignment: alignment of the pointer to I must be equal or greater than the alignment of O"),
TransmuteError::Length => write!(formatter, "Length: I's items cant fit in O's (e.g. 1 u8 to 1 u16)"),
TransmuteError::Capacity => write!(formatter, "Capacity: capacicty of vec is incorrect"),
}
}
}
#[cfg(feature = "std")]
impl Error for TransmuteError {}
#[cfg(feature = "allocator_api")]
#[derive(Debug)]
pub struct AlignmentCorrectorAllocator<I, O, A: Allocator> {
allocator: A,
ptr: AtomicPtr<O>,
phantom: PhantomData<I>,
}
#[cfg(feature = "allocator_api")]
impl<I, O, A: Allocator> AlignmentCorrectorAllocator<I, O, A> {
pub unsafe fn new(ptr: NonNull<O>, allocator: A) -> Self {
Self {
allocator,
ptr: AtomicPtr::new(ptr.as_ptr()),
phantom: PhantomData::default(),
}
}
pub fn new_null(allocator: A) -> Self {
Self {
allocator,
ptr: AtomicPtr::new(ptr::null_mut()),
phantom: PhantomData::default(),
}
}
pub fn is_null(&self) -> bool {
self.ptr.load(atomic::Ordering::Relaxed).is_null()
}
pub fn into_inner(self) -> Result<A, Self> {
if self.is_null() {
Ok(self.allocator)
} else {
Err(self)
}
}
unsafe fn get_layout(&self, mut layout: Layout) -> Layout {
let mut old = self.ptr.load(atomic::Ordering::Relaxed);
if old.is_null() {
return layout;
}
loop {
match self.ptr.compare_exchange_weak(
old,
ptr::null_mut(),
atomic::Ordering::SeqCst,
atomic::Ordering::Relaxed,
) {
Ok(x) if !x.is_null() => {
layout =
unsafe { Layout::from_size_align_unchecked(layout.size(), mem::align_of::<I>()) };
break layout;
}
Ok(_) => break layout,
Err(x) if x.is_null() => break layout,
Err(x) => old = x,
}
}
}
}
#[cfg(feature = "allocator_api")]
unsafe impl<I, O, A: Allocator> Allocator for AlignmentCorrectorAllocator<I, O, A> {
#[inline]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
self.allocator.allocate(layout)
}
#[inline]
fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
self.allocator.allocate_zeroed(layout)
}
#[inline]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { self.allocator.deallocate(ptr, self.get_layout(layout)) }
}
#[inline]
unsafe fn grow(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
unsafe {
self.allocator
.grow(ptr, self.get_layout(old_layout), new_layout)
}
}
#[inline]
unsafe fn grow_zeroed(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
unsafe {
self.allocator
.grow_zeroed(ptr, self.get_layout(old_layout), new_layout)
}
}
#[inline]
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
unsafe { self.allocator.shrink(ptr, old_layout, new_layout) }
}
}
#[cfg(feature = "allocator_api")]
unsafe fn from_raw_parts<I, O, A: Allocator>(
old_ptr: NonNull<O>,
length: usize,
capacity: usize,
allocator: A,
) -> Vec<O, AlignmentCorrectorAllocator<I, O, A>> {
unsafe {
Vec::<O, AlignmentCorrectorAllocator<I, O, A>>::from_raw_parts_in(
old_ptr.as_ptr(),
length,
capacity,
AlignmentCorrectorAllocator::<I, O, A>::new(old_ptr, allocator),
)
}
}
#[cfg(feature = "allocator_api")]
pub enum CopyNot<I, O, A: Allocator> {
Copy(Vec<O, A>),
Not(Vec<O, AlignmentCorrectorAllocator<I, O, A>>),
}
#[cfg(feature = "allocator_api")]
pub fn transmute_vec_copy_enum<I: Pod, O: Pod, A: Allocator>(input: Vec<I, A>) -> CopyNot<I, O, A> {
unsafe { transmute_vec_copy_enum_unsafe(input) }
}
#[cfg(feature = "allocator_api")]
pub unsafe fn transmute_vec_copy_enum_unsafe<I, O, A: Allocator>(
input: Vec<I, A>,
) -> CopyNot<I, O, A> {
match unsafe { transmute_vec_unsafe(input) } {
Ok(x) => CopyNot::Not(x),
Err((old_vec, err)) => match err {
TransmuteError::Alignment => {
let (ptr, length, capacity, allocator) = {
let mut me = ManuallyDrop::new(old_vec);
(me.as_mut_ptr(), me.len(), me.capacity(), unsafe {
ptr::read(me.allocator())
})
};
let bytes_slice = unsafe {
slice::from_raw_parts(ptr.cast::<u8>(), length * mem::size_of::<I>())
};
let mut return_vec = Vec::with_capacity_in(
(length * mem::size_of::<I>()) / mem::size_of::<O>(),
allocator,
);
for x in bytes_slice.chunks_exact(mem::size_of::<O>()) {
return_vec.push(unsafe { ptr::read_unaligned(x.as_ptr().cast()) });
}
unsafe {
let align = mem::align_of::<I>();
let size = mem::size_of::<I>() * capacity;
let layout = Layout::from_size_align_unchecked(size, align);
if size != 0 {
return_vec
.allocator()
.deallocate(NonNull::new_unchecked(ptr.cast()), layout);
}
};
CopyNot::Copy(return_vec)
}
TransmuteError::Capacity | TransmuteError::Length => {
let (ptr, length, capacity, allocator) = {
let mut me = ManuallyDrop::new(old_vec);
(me.as_mut_ptr(), me.len(), me.capacity(), unsafe {
ptr::read(me.allocator())
})
};
let new_length = (length * mem::size_of::<I>()) / mem::size_of::<O>();
let mut return_vec = Vec::with_capacity_in(new_length, allocator);
unsafe {
ptr::copy_nonoverlapping(ptr as *const O, return_vec.as_mut_ptr(), new_length);
return_vec.set_len(new_length);
}
unsafe { deallocate_for_vec(ptr, length, capacity, return_vec.allocator()) }
CopyNot::Copy(return_vec)
}
},
}
}
#[cfg(feature = "allocator_api")]
pub fn add_alignment_allocator<I: Pod, O: Pod, A: Allocator>(
input: Vec<O, A>,
) -> Vec<O, AlignmentCorrectorAllocator<I, O, A>> {
let (ptr, length, capacity, allocator) = {
let mut me = ManuallyDrop::new(input);
(me.as_mut_ptr(), me.len(), me.capacity(), unsafe {
ptr::read(me.allocator())
})
};
unsafe {
Vec::from_raw_parts_in(
ptr,
length,
capacity,
AlignmentCorrectorAllocator::new_null(allocator),
)
}
}
#[cfg(feature = "allocator_api")]
pub fn remove_alignment_allocator<I: Pod, O: Pod, A: Allocator>(
input: Vec<O, AlignmentCorrectorAllocator<I, O, A>>,
) -> Result<Vec<O, A>, Vec<O, AlignmentCorrectorAllocator<I, O, A>>> {
let (ptr, length, capacity, allocator) = {
let mut me = ManuallyDrop::new(input);
(me.as_mut_ptr(), me.len(), me.capacity(), unsafe {
ptr::read(me.allocator())
})
};
unsafe {
match allocator.into_inner() {
Ok(allocator) => Ok(Vec::from_raw_parts_in(ptr, length, capacity, allocator)),
Err(allocator) => Err(Vec::from_raw_parts_in(ptr, length, capacity, allocator)),
}
}
}
#[cfg(feature = "allocator_api")]
pub fn transmute_vec_may_copy<I: Pod, O: Pod, A: Allocator>(
input: Vec<I, A>,
) -> Vec<O, AlignmentCorrectorAllocator<I, O, A>> {
match transmute_vec_copy_enum(input) {
CopyNot::Copy(x) => add_alignment_allocator(x),
CopyNot::Not(x) => x,
}
}
#[cfg(feature = "allocator_api")]
pub unsafe fn transmute_vec_may_copy_unsafe<I: Pod, O: Pod, A: Allocator>(
input: Vec<I, A>,
) -> Vec<O, AlignmentCorrectorAllocator<I, O, A>> {
match unsafe { transmute_vec_copy_enum_unsafe(input) } {
CopyNot::Copy(x) => add_alignment_allocator(x),
CopyNot::Not(x) => x,
}
}
#[cfg(feature = "allocator_api")]
unsafe fn deallocate_for_vec<I, A: Allocator>(
ptr: *mut I,
length: usize,
capacity: usize,
allocator: A,
) {
unsafe {
let align = mem::align_of::<I>();
let size = mem::size_of::<I>() * capacity;
if size != 0 {
ptr::drop_in_place(ptr::slice_from_raw_parts_mut(ptr, length));
let layout = Layout::from_size_align_unchecked(size, align);
allocator.deallocate(NonNull::new_unchecked(ptr.cast()), layout);
}
}
}
#[cfg(feature = "allocator_api")]
#[allow(clippy::type_complexity)]
pub unsafe fn transmute_vec_unsafe<I, O, A: Allocator>(
input: Vec<I, A>,
) -> Result<Vec<O, AlignmentCorrectorAllocator<I, O, A>>, (Vec<I, A>, TransmuteError)> {
let (ptr, length, capacity, allocator) = {
let mut me = ManuallyDrop::new(input);
(me.as_mut_ptr(), me.len(), me.capacity(), unsafe {
ptr::read(me.allocator())
})
};
if mem::size_of::<O>() == 0 {
unsafe {
deallocate_for_vec(ptr, length, capacity, &allocator);
}
let mut return_vec =
Vec::with_capacity_in(capacity, AlignmentCorrectorAllocator::new_null(allocator));
unsafe { return_vec.set_len(length) };
return Ok(return_vec);
} else if mem::size_of::<I>() == 0 || capacity == 0 {
unsafe {
deallocate_for_vec(ptr, length, capacity, &allocator);
}
return Ok(Vec::new_in(AlignmentCorrectorAllocator::new_null(
allocator,
)));
}
match mem::size_of::<I>().cmp(&mem::size_of::<O>()) {
Ordering::Greater | Ordering::Less => {
if ptr.align_offset(mem::align_of::<O>()) != 0 {
Err((
unsafe { Vec::from_raw_parts_in(ptr, length, capacity, allocator) },
TransmuteError::Alignment,
))
} else if (length * mem::size_of::<I>()) % mem::size_of::<O>() != 0 {
Err((
unsafe { Vec::from_raw_parts_in(ptr, length, capacity, allocator) },
TransmuteError::Length,
))
} else if (capacity * mem::size_of::<I>()) % mem::size_of::<O>() != 0 {
Err((
unsafe { Vec::from_raw_parts_in(ptr, length, capacity, allocator) },
TransmuteError::Capacity,
))
} else {
Ok(unsafe {
from_raw_parts(
NonNull::new_unchecked(ptr).cast(),
(length * mem::size_of::<I>()) / mem::size_of::<O>(),
(capacity * mem::size_of::<I>()) / mem::size_of::<O>(),
allocator,
)
})
}
}
Ordering::Equal => {
if ptr.align_offset(mem::align_of::<O>()) == 0 {
Ok(unsafe {
from_raw_parts(
NonNull::new_unchecked(ptr).cast(),
length,
capacity,
allocator,
)
})
} else {
Err((
unsafe { Vec::from_raw_parts_in(ptr, length, capacity, allocator) },
TransmuteError::Alignment,
))
}
}
}
}
#[cfg(feature = "allocator_api")]
pub fn transmute_vec<I: Pod, O: Pod, A: Allocator>(
input: Vec<I, A>,
) -> Result<Vec<O, AlignmentCorrectorAllocator<I, O, A>>, (Vec<I, A>, TransmuteError)> {
unsafe { transmute_vec_unsafe(input) }
}
pub fn transmute_vec_basic<I: Pod, O: Pod>(
input: Vec<I>,
) -> Result<Vec<O>, (Vec<I>, TransmuteError)> {
unsafe { transmute_vec_basic_unsafe(input) }
}
pub unsafe fn transmute_vec_basic_unsafe<I, O>(
input: Vec<I>,
) -> Result<Vec<O>, (Vec<I>, TransmuteError)> {
let (ptr, length, capacity) = {
let mut me = ManuallyDrop::new(input);
(me.as_mut_ptr(), me.len(), me.capacity())
};
if mem::align_of::<I>() != mem::align_of::<O>() {
panic!(
"Alignment of {} and {} are not the same",
core::any::type_name::<I>(),
core::any::type_name::<O>(),
);
} else if mem::size_of::<O>() == 0 {
drop(unsafe { Vec::from_raw_parts(ptr, length, capacity) });
let mut vec = Vec::with_capacity(capacity);
unsafe { vec.set_len(length) };
return Ok(vec);
} else if mem::size_of::<I>() == 0 || capacity == 0 {
drop(unsafe { Vec::from_raw_parts(ptr, length, capacity) });
return Ok(Vec::new());
}
match mem::size_of::<I>().cmp(&mem::size_of::<O>()) {
Ordering::Greater | Ordering::Less => {
if (length * mem::size_of::<I>()) % mem::size_of::<O>() != 0 {
Err((
unsafe { Vec::from_raw_parts(ptr, length, capacity) },
TransmuteError::Length,
))
} else if (capacity * mem::size_of::<I>()) % mem::size_of::<O>() != 0 {
Err((
unsafe { Vec::from_raw_parts(ptr, length, capacity) },
TransmuteError::Capacity,
))
} else {
Ok(unsafe {
Vec::from_raw_parts(
ptr.cast(),
(length * mem::size_of::<I>()) / mem::size_of::<O>(),
(capacity * mem::size_of::<I>()) / mem::size_of::<O>(),
)
})
}
}
Ordering::Equal => {
Ok(unsafe {
Vec::from_raw_parts(
ptr.cast(),
(length * mem::size_of::<I>()) / mem::size_of::<O>(),
(capacity * mem::size_of::<I>()) / mem::size_of::<O>(),
)
})
}
}
}
pub unsafe fn transmute_vec_basic_copy_unsafe<I, O>(input: Vec<I>) -> Vec<O> {
match unsafe { transmute_vec_basic_unsafe(input) } {
Ok(x) => x,
Err((mut old_vec, err)) => match err {
TransmuteError::Alignment => unsafe { hint::unreachable_unchecked() },
TransmuteError::Length | TransmuteError::Capacity => {
let length = old_vec.len();
let ptr = old_vec.as_mut_ptr();
let mut new_vec = Vec::with_capacity(length);
unsafe {
ptr::copy_nonoverlapping(ptr, new_vec.as_mut_ptr() as *mut I, length);
new_vec.set_len(length);
}
new_vec
}
},
}
}
#[must_use = "You shouldn't use this function if you're not going to use the result as it's useless otherwise"]
pub fn transmute_vec_basic_copy<I: Pod, O: Pod>(input: Vec<I>) -> Vec<O> {
match transmute_vec_basic(input) {
Ok(x) => x,
Err((old_vec, err)) => match err {
TransmuteError::Alignment => unsafe { hint::unreachable_unchecked() },
TransmuteError::Capacity | TransmuteError::Length => {
let ptr = old_vec.as_ptr();
let length = old_vec.len();
unsafe {
slice::from_raw_parts(
ptr.cast::<O>(),
(length * mem::size_of::<I>()) / mem::size_of::<O>(),
)
}
.to_vec()
}
},
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "allocator_api")]
use crate::{transmute_vec, transmute_vec_may_copy};
use crate::{transmute_vec_basic, transmute_vec_basic_copy, TransmuteError};
use alloc::{vec, vec::Vec};
#[test]
#[cfg(feature = "allocator_api")]
fn basic_functioning() {
let input: Vec<u8> = vec![0, 1, 2, 3, 4, 6];
let output: Vec<i8, _> = match transmute_vec(input) {
Ok(x) => x,
Err(_) => return,
};
assert_eq!(&output, &[0, 1, 2, 3, 4, 6]);
}
#[test]
fn basic_basic_functioning() {
let input: Vec<u8> = vec![0, 1, 2, 3, 4, 6];
let output: Vec<i8> = match transmute_vec_basic(input) {
Ok(x) => x,
Err(_) => return,
};
assert_eq!(&output, &[0, 1, 2, 3, 4, 6]);
}
#[test]
#[should_panic]
fn unalign_panic() {
let _ = transmute_vec_basic::<u8, u16>(Vec::new());
}
#[test]
fn different_size_same_align() {
let input: Vec<u8> = vec![0, 1, 2, 3, 4, 6];
let output: Vec<[u8; 2]> = match transmute_vec_basic(input) {
Ok(x) => x,
Err(_) => return,
};
assert_eq!(&output, &[[0, 1], [2, 3], [4, 6]])
}
#[test]
#[cfg(feature = "allocator_api")]
fn small_to_large() {
let input: Vec<u8> = vec![0, 1, 2, 3, 4, 6];
let output: Vec<u16, _> = match transmute_vec(input) {
Ok(x) => x,
Err(_) => return,
};
if cfg!(target_endian = "big") {
assert_eq!(&output, &[1, 515, 1030]);
} else {
assert_eq!(&output, &[256, 770, 1540]);
}
}
#[test]
#[cfg(feature = "allocator_api")]
fn large_to_small() {
let input: Vec<u16> = vec![1, 2, 3, 4, 5, 6, 7, 8];
let output: Vec<u8, _> = match transmute_vec(input) {
Ok(x) => x,
Err(_) => return,
};
if cfg!(target_endian = "big") {
assert_eq!(&output, &[0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]);
} else {
assert_eq!(&output, &[1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0]);
}
}
#[test]
fn to_zsts_basic() {
let input: Vec<u8> = vec![0, 1, 2, 3, 4, 5];
let output: Vec<()> = match transmute_vec_basic(input) {
Ok(x) => x,
Err(_) => return,
};
assert_eq!(output.len(), 6);
}
#[test]
#[cfg(feature = "allocator_api")]
fn add_and_remove() {
let input: Vec<u16> = vec![1, 2, 3, 4, 5, 6, 7, 8];
let mut output: Vec<u8, _> = match transmute_vec(input) {
Ok(x) => x,
Err(_) => return,
};
output.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9]);
for _ in 0..10 {
output.pop();
}
output.shrink_to_fit()
}
#[test]
#[cfg(feature = "allocator_api")]
fn wrong_length() {
let input: Vec<u8> = vec![1, 2, 3];
match transmute_vec::<_, u16, _>(input) {
Ok(_) => panic!(),
Err((_, err)) => match err {
TransmuteError::Alignment | TransmuteError::Length => (),
x => panic!("{:?}", x),
},
};
}
#[test]
#[cfg(feature = "allocator_api")]
fn wrong_length_copy() {
let input: Vec<u8> = vec![1, 2, 3];
let output: Vec<u16, _> = transmute_vec_may_copy::<_, u16, _>(input);
if cfg!(target_endian = "big") {
assert_eq!(&output, &[258]);
} else {
assert_eq!(&output, &[513]);
}
}
#[test]
#[cfg(feature = "allocator_api")]
fn may_copy() {
let input: Vec<u8> = vec![1, 2];
let output: Vec<u16, _> = transmute_vec_may_copy::<_, u16, _>(input);
if cfg!(target_endian = "big") {
assert_eq!(&output, &[258]);
} else {
assert_eq!(&output, &[513]);
}
}
#[test]
fn basic_copy() {
let input: Vec<u8> = vec![1, 2];
let output: Vec<[u8; 2]> = transmute_vec_basic_copy(input);
assert_eq!(&output, &[[1, 2]]);
}
#[test]
#[cfg(feature = "allocator_api")]
fn from_zsts() {
let input = vec![(), (), ()];
let output: Vec<u8, _> = match transmute_vec(input) {
Ok(x) => x,
Err(_) => return,
};
assert_eq!(output.len(), 0);
}
#[test]
#[cfg(feature = "allocator_api")]
fn to_zsts() {
let input: Vec<u8> = vec![0, 1, 2, 3, 4, 5];
let output: Vec<(), _> = match transmute_vec(input) {
Ok(x) => x,
Err(_) => return,
};
assert_eq!(output.len(), 6);
}
}