Using Box::from_raw
like this is not allowed by the API. If you check the documentation for Box::from_raw
it says the following.
For this to be safe, the memory must have been allocated in accordance with the memory layout used by Box
.
What does allocation in accordance with the memory layout mean?
It is valid to convert both ways between a Box
and a raw pointer allocated with the Global
allocator, given that the Layout
used with the allocator is correct for the type.
Alignment is part of a layout, so if it gets changed the layouts are incompatible and UB occurs. This may be unnoticed with standard allocator (malloc
) as standard allocator ignores alignment arguments, but it will be noticed by tools like Miri. For instance, consider the following program.
use std::alloc::{alloc, Layout};
fn main() {
unsafe {
let ptr = alloc(Layout::from_size_align(1, 4).unwrap());
*ptr = 42;
Box::from_raw(ptr);
}
}
This happens to work because by default Rust uses malloc
and free
functions from C standard library that don't have an alignment parameter. However, alternate allocators can take alignment parameter and a mismatch here would cause UB. For instance, when running this program in Miri the following error occurs.
error: Undefined Behavior: incorrect layout on deallocation: allocation has size 1 and alignment 4, but gave size 1 and alignment 1
--> src/main.rs:7:27
|
7 | Box::from_raw(ptr);
| ^
So what could be done about that? Well, we can wrap our unsized array into an alignment structure. To do so, we need to allocate memory using raw APIs, create a reference to unsized [T]
by using slice::from_raw_parts_mut
, cast the reference to our wrapped type and then create a box. When a box gets deallocated the alignments will match.
use std::alloc::{alloc, handle_alloc_error, Layout};
use std::mem::{self, MaybeUninit};
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use std::slice;
#[repr(C, align(4))]
struct Align4<T: ?Sized>(T);
impl<T: ?Sized> Deref for Align4<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: ?Sized> DerefMut for Align4<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
fn allocate_align4_slice<T>(len: usize) -> Box<Align4<[MaybeUninit<T>]>> {
let bytes = mem::size_of::<T>().checked_mul(len).expect("overflow");
unsafe {
let allocation = if bytes == 0 {
NonNull::<Align4<T>>::dangling().as_ptr() as *mut u8
} else {
let layout = Layout::from_size_align(bytes, mem::align_of::<Align4<T>>()).unwrap();
let allocation = alloc(layout);
if allocation.is_null() {
handle_alloc_error(layout);
}
allocation
};
let slice = slice::from_raw_parts_mut(allocation as *mut MaybeUninit<T>, len);
Box::from_raw(slice as *mut [MaybeUninit<T>] as *mut Align4<[MaybeUninit<T>]>)
}
}
fn main() {
let mut boxed = allocate_align4_slice(42);
unsafe { *boxed[0].as_mut_ptr() = 88 }
}
But that helps with Box
only, what about Arc
? Well, as far I can tell there is no way to avoid an additional allocation. If an allocation is fine, Arc::from(boxed)
should work fine.
If you don't have an alignment requirement, it's possible to use collect()
to create Arc<[MaybeUninit<u8>]>
. This is done by writing iter::repeat(|| MaybeUninit::uninit()).take(len).collect::<Arc<[MaybeUninit<u8>]>>()
.
However, this won't help when the data needs to be aligned in a particular way as there is no API to allocate memory with a provided Layout
in a way that Arc::from_raw
expects it to be allocated (RFC candidate maybe?). The API that would allow this could look like fn alloc_raw(layout: Layout) -> *mut u8
.