rustrust-no-stdconst-generics

Const context: Create array with const generic length from init function


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:


Solution

  • The ideal solution would be either:

    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.