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.
Things I've tried so far:
-
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 exampleimpl<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
andfeature
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 instable
.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 andfeature
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 } } }