What that snippet does is UB: a Vec<T>
expects the backing heap-allocated [T; capacity]
buffer to be T
-aligned. Constructing a Vec
with a manually heap-allocated buffer with lower or higher alignment is forbidden.
Usually the trick with higher alignement is to start like that post did, with a Vec<AlignedTo<64, [u8; 64]>>
, for instance, and never leave it.
use {
::core::mem::align_of,
::elain::{Align, Alignment},
};
#[repr(C)]
struct AlignedTo<const MIN_ALIGNMENT: usize, T = [u8; MIN_ALIGNMENT]> (
Align<MIN_ALIGNMENT>,
T,
)
where
Align<MIN_ALIGNMENT> : Alignment,
;
impl Deref{,Mut} for … {
type Target = T;
}
One question which may make things way easier, first and foremost, is thus:
do you need the a posteriori size-changing capabilities of a Vec
?
-
If not, then things can become way easier, because any impl 'static + DerefMut<Target = [u8]>
will behave like a Box<[u8]>
, API-wise, and thus hide that inner AlignedTo<64, [u8; 64]>
type.
-
Else things become messier, because you'll have to keep your own Vec
newtype around, somehow exposing those details.
Going for the former, you could then have:
// Desired alignement
desired_alignment!(0x40);
macro_rules! desired_alignment {( $A:tt ) => (
#[derive(Clone, Copy)]
#[repr(C, align($A))]
struct AlignedBytes([u8; $A]);
pub
struct BoxAlignedBytes /* = */ (
Box<[AlignedBytes]>,
);
impl ::core::ops::Deref for BoxAlignedBytes {
type Target = [u8];
fn deref (self: &'_ BoxAlignedBytes)
-> &'_ [u8]
{
unsafe {
::core::slice::from_raw_parts(
::core::ptr::addr_of!(*self.0).cast(),
self.0.len() * $A,
)
}
}
}
impl ::core::ops::DerefMut for BoxAlignedBytes {
fn deref_mut (self: &'_ mut BoxAlignedBytes)
-> &'_ mut [u8]
{
unsafe {
::core::slice::from_raw_parts_mut(
::core::ptr::addr_of_mut!(*self.0).cast(),
self.0.len() * $A,
)
}
}
}
impl<'r> From<&'r [u8]> for BoxAlignedBytes {
fn from (buf: &'r [u8])
-> BoxAlignedBytes
{
let mut len = buf.len();
if len % $A > 0 {
// pad with `0`s.
len += ($A - len % $A);
}
let count = len / $A;
let mut boxed = Self(
vec![AlignedBytes([0; $A]); count]
.into_boxed_slice()
);
boxed[.. buf.len()].copy_from_slice(buf);
boxed
}
}
)} use desired_alignment;
If you wanted Vec
growability capabilities, you'd have to stick to a Vec<AlignedBytes>
and handle the .len()
and .capacity()
indexing over those big AlignedBytes
contiguous chunks, basically reinventing Vec
's API, but amended to fit this situation.
This is doable, but it requires way more unsafe
: you'll have to play with the Allocator
API yourself, by constructing a runtime Layout
value. Afterwards, exposing a wrapper around this with BoxAlignedBytes
API should be easy; but if you want both runtime alignment and growability it's gonna be very painful, and error-prone.
- How is it that you need a runtime alignement, may I ask? Does a
const
-generic not suffice? Alignment needs are supposed to be known at compile-time by the end user, so a const
generic (+ elain - Rust) ought to do the job