Move elements out of array in const context
⚓ Rust 📅 2025-10-07 👤 surdeus 👁️ 5I'm trying to figure out the preferred methods to:
- move elements out of an array
- destructure objects
on stable in a const context.
In the working example below we transpose the type of an array of structs into a struct of arrays.
The solutions I've come up with for moving elements out of an array is to:
- wrap the array in
ManuallyDrop. ptr::readeach element once.
Currently we can't destructure types in a const context despite them not implementing Drop. Instead of reading out the element and destructuring, we read out it's fields.
use std::{
mem::{ManuallyDrop, MaybeUninit},
ptr::addr_of,
};
pub struct S<T> {
pub count: T,
pub stride: T,
}
/// Transpose the type from an array of structs into a struct of arrays.
#[inline]
pub const fn into_soa<T, const N: usize>(shape: [S<T>; N]) -> S<[T; N]> {
let mut stride = array_uninit();
let mut count = array_uninit();
let shape = ManuallyDrop::new(shape);
// Work around our inability to obtain a reference to the inner value in a const context.
let shape: &[S<T>; N] = manually_drop_inner_ref(&shape);
const_for!(n in 0..N => {
let shape = &shape[n];
// SAFETY: The original will not be dropped and we only create one copy of each stride.
stride[n].write(unsafe { addr_of!(shape.stride).read() });
// SAFETY: The original will not be dropped and we only create one copy of each count.
count[n].write(unsafe { addr_of!(shape.count).read() });
});
// SAFETY: All elements have been written to.
let stride = unsafe { array_assume_init(stride) };
// SAFETY: All elements have been written to.
let count = unsafe { array_assume_init(count) };
S { stride, count }
}
#[cfg(test)]
mod tests {
use super::*;
const_matches!(
into_soa([S { stride: 1, count: 2 }, S { stride: 3, count: 4 }]),
S {
stride: [1, 3],
count: [2, 4]
}
);
}
/// Create an array of uninitialized elements.
pub(crate) const fn array_uninit<T, const N: usize>() -> [MaybeUninit<T>; N] {
[const { MaybeUninit::uninit() }; N] // <- is the const { ... } here necessary?
}
// TODO: Replace with stable version when stabilized.
pub(crate) const unsafe fn array_assume_init<T, const N: usize>(array: [MaybeUninit<T>; N]) -> [T; N] {
// SAFETY: MaybeUninit<T> and T are guaranteed to have the same layout
unsafe { ::core::mem::transmute_copy(&array) }
}
/// Provides access to the inner value of a ManuallyDrop<T>.
pub(crate) const fn manually_drop_inner_ref<T>(slot: &ManuallyDrop<T>) -> &T {
// SAFETY: ManuallyDrop<T> and T are guaranteed to have the same layout
unsafe { std::mem::transmute(slot) }
}
// inspired by crate const_for
#[macro_export]
macro_rules! const_for {
($var:pat_param in $range:expr => $body:stmt) => {
let ::core::ops::Range { start: mut index, end } = $range;
while index < end {
let $var = index;
$body
index += 1;
}
};
}
// inspired by crate static_assertions
#[macro_export]
macro_rules! const_matches {
($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) => {
const _: [(); 1] = [(); matches!($expression, $pattern $(if $guard)?) as usize];
};
}
Is the code sound, and is there a better implementation?
1 post - 1 participant
🏷️ Rust_feed