Denis Steinman
Denis Steinman

Reputation: 7799

How can I create a fixed size array of Strings using constant generics?

I have a function using a constant generic:

fn foo<const S: usize>() -> Vec<[String; S]> {
    // Some code
    let mut row: [String; S] = Default::default(); //It sucks because of default arrays are specified up to 32 only
    // Some code
}

How can I create a fixed size array of Strings in my case? let mut row: [String; S] = ["".to_string(), S]; doesn't work because String doesn't implement the Copy trait.

Upvotes: 2

Views: 1210

Answers (2)

eggyal
eggyal

Reputation: 125975

You can do it with MaybeUninit and unsafe:

use std::mem::MaybeUninit;

fn foo<const S: usize>() -> Vec<[String; S]> {
    // Some code

    let mut row: [String; S] = unsafe {
        let mut result = MaybeUninit::uninit();
        let start = result.as_mut_ptr() as *mut String;
        
        for pos in 0 .. S {
            // SAFETY: safe because loop ensures `start.add(pos)`
            //         is always on an array element, of type String
            start.add(pos).write(String::new());
        }

        // SAFETY: safe because loop ensures entire array
        //         has been manually initialised
        result.assume_init()
    };

    // Some code

    todo!()
}

Of course, it might be easier to abstract such logic to your own trait:

use std::mem::MaybeUninit;

trait DefaultArray {
    fn default_array() -> Self;
}

impl<T: Default, const S: usize> DefaultArray for [T; S] {
    fn default_array() -> Self {
        let mut result = MaybeUninit::uninit();
        let start = result.as_mut_ptr() as *mut T;
        
        unsafe {
            for pos in 0 .. S {
                // SAFETY: safe because loop ensures `start.add(pos)`
                //         is always on an array element, of type T
                start.add(pos).write(T::default());
            }

            // SAFETY: safe because loop ensures entire array
            //         has been manually initialised
            result.assume_init()
        }
    }
}

(The only reason for using your own trait rather than Default is that implementations of the latter would conflict with those provided in the standard library for arrays of up to 32 elements; I wholly expect the standard library to replace its implementation of Default with something similar to the above once const generics have stabilised).

In which case you would now have:

fn foo<const S: usize>() -> Vec<[String; S]> {
    // Some code

    let mut row: [String; S] = DefaultArray::default_array();

    // Some code

    todo!()
}

See it on the Playground.

Upvotes: 5

Fluffyeater
Fluffyeater

Reputation: 35

As of now, there is no way to compile constant generics. As @AlexLarionov said, you can try to use procedural macros, but that approach still has its bugs and limitations.

If you need a generic that has to be a number, you can use the Num crate, or the more verbose std::num.

Upvotes: -2

Related Questions