I'm trying to create a container object containing a const array using a const initializer function for each element.
For arrays of a fixed size (notated with an integer literal) this is already solved; the twist here is that the length of the array is a const generic.
A further twist is that I need this entire thing in no_std
(core) mode.
Here is a code example that demonstrates my problem:
// Important: Element is **not** `Copy`.
#[derive(Debug)]
struct Element(usize);
impl Element {
pub const fn array_initializer(pos: usize) -> Self {
Element(pos)
}
}
#[derive(Debug)]
pub struct Container<const N: usize> {
data: [Element; N],
}
impl<const N: usize> Container<N> {
pub const fn new() -> Self {
// The content of this function is the only
// part that is open for change!
// Task is: create a container with `N` elements,
// where every element is initialized with
// `Element::array_initializer`.
}
}
static STATIC_CONTAINER: Container<5> = Container::new();
fn main() {
println!("{:?}", STATIC_CONTAINER);
}
Desired output:
Container { data: [Element(0), Element(1), Element(2), Element(3), Element(4)] }
Of course there also might be the possibility that this is entirely impossible in Rust's current state; although I would be quite sad about it.
I don't care about the use of unsafe
in the Container::new
function, as long as it is sound.
It fails because it does not accept const generic array lengths.
impl<const N: usize> Container<N> {
pub const fn new() -> Self {
const fn element_init(pos: usize) -> Element {
Element::array_initializer(pos)
}
let data: [Element; N] = array_const_fn_init::array_const_fn_init!(element_init; N);
Self { data }
}
}
error: proc macro panicked
--> src/main.rs:21:34
|
21 | let data: [Element; N] = array_const_fn_init::array_const_fn_init!(element_init; N);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: message: Expected <usize>, found N
MaybeUninit
, based on this example
impl<const N: usize> Container<N> {
pub const fn new() -> Self {
use core::mem::MaybeUninit;
let mut data: [MaybeUninit<Element>; N] = unsafe { MaybeUninit::uninit().assume_init() };
{
// const for's are not stabilized yet, so use a loop
let mut i = 0;
while i < N {
data[i] = MaybeUninit::new(Element::array_initializer(i));
i += 1;
}
}
let data: [Element; N] =
unsafe { core::mem::transmute::<[MaybeUninit<Element>; N], [Element; N]>(data) };
Self { data }
}
}
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
--> src/main.rs:30:22
|
37 | unsafe { core::mem::transmute::<[MaybeUninit<Element>; N], [Element; N]>(data) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: source type: `[MaybeUninit<Element>; N]` (this type does not have a fixed size)
= note: target type: `[Element; N]` (this type does not have a fixed size)
This is technically nonsense and probably a compiler restriction that will be lifted in future; [MaybeUninit<Element>; N]
and [Element; N]
definitely do have the same size.
core::intrinsics::transmute_unchecked
As we know the size is identical, use transmute_unchecked
to do the transmutation anyway.
Now we are in nightly
and feature
territory, which sadly isn't compatible with my situation :/
#![feature(core_intrinsics)]
// ...
impl<const N: usize> Container<N> {
pub const fn new() -> Self {
use core::mem::MaybeUninit;
let mut data: [MaybeUninit<Element>; N] = unsafe { MaybeUninit::uninit().assume_init() };
{
// const for's are not stabilized yet, so use a loop
let mut i = 0;
while i < N {
data[i] = MaybeUninit::new(Element::array_initializer(i));
i += 1;
}
}
let data: [Element; N] = unsafe {
core::intrinsics::transmute_unchecked::<[MaybeUninit<Element>; N], [Element; N]>(data)
};
Self { data }
}
}
This does give us the correct result, but feature(core_intrinsics)
probably won't ever get included in stable
.
MaybeUninit::array_assume_init
This does seem a little bit saner and a little bit closer to stabilization, but currently it still requires a nightly
compiler and feature
gates.
#![feature(maybe_uninit_array_assume_init)]
#![feature(const_maybe_uninit_array_assume_init)]
// ...
impl<const N: usize> Container<N> {
pub const fn new() -> Self {
use core::mem::MaybeUninit;
let mut data: [MaybeUninit<Element>; N] = unsafe { MaybeUninit::uninit().assume_init() };
{
// const for's are not stabilized yet, so use a loop
let mut i = 0;
while i < N {
data[i] = MaybeUninit::new(Element::array_initializer(i));
i += 1;
}
}
let data: [Element; N] = unsafe { MaybeUninit::array_assume_init(data) };
Self { data }
}
}
The ideal solution would be either:
core::array::from_fn
(not even an RFC for its const
ness exists yet)MaybeUninit::array_assume_init
(not stabilized yet at the time of writing)So at the current time, it seems that a union
based transmutation is the most sane option I could find:
// Important: Element is **not** `Copy`.
#[derive(Debug)]
struct Element(usize);
impl Element {
pub const fn array_initializer(pos: usize) -> Self {
Element(pos)
}
}
#[derive(Debug)]
pub struct Container<const N: usize> {
data: [Element; N],
}
impl<const N: usize> Container<N> {
pub const fn new() -> Self {
use core::mem::{ManuallyDrop, MaybeUninit};
let mut data: [MaybeUninit<Element>; N] = unsafe { MaybeUninit::uninit().assume_init() };
{
// const for's are not stabilized yet, so use a loop
let mut i = 0;
while i < N {
data[i] = MaybeUninit::new(Element::array_initializer(i));
i += 1;
}
}
#[repr(C)]
union InitializedData<const N: usize> {
uninit: ManuallyDrop<[MaybeUninit<Element>; N]>,
init: ManuallyDrop<[Element; N]>,
}
let data = ManuallyDrop::into_inner(unsafe {
InitializedData {
uninit: ManuallyDrop::new(data),
}
.init
});
Self { data }
}
}
static STATIC_CONTAINER: Container<5> = Container::new();
fn main() {
println!("{:?}", STATIC_CONTAINER);
}
Container { data: [Element(0), Element(1), Element(2), Element(3), Element(4)] }
It is important here that Element::array_initializer
can't panic, otherwise there might have to be additional considerations regarding Drop
behaviour.