2

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.

7
  • 1
    You may try to play with procedural macros, but it's quite verbose and doesn't compile on stable Commented Aug 17, 2020 at 12:29
  • 1
    @AlexLarionov const generics don't compile on stable either. Commented Aug 17, 2020 at 12:29
  • 2
    Instead you can return Vec<String> and process rows/columns separation in code Commented Aug 17, 2020 at 12:30
  • @AlexLarionov, yes, I know about Vec but I hoped there is a way to use a fixed size array. Commented Aug 17, 2020 at 12:32
  • arrays are specified up to 32 only - Wasn't that limitation lifted? Commented Aug 17, 2020 at 16:42

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

24 Comments

Beautiful answer, love the generalization.
Should the semicolon after T: Default be a comma instead? Also (and this could just be a matter of style), you could use the pointer arithmetic without mutating ptr, relying on the optimizer to do the right thing: playground. That way the relation between the pointer and the array is a bit more direct.
@user4815162342: Yes, it should have been a comma—already updated, thanks. And yes, that's valid too.
@user4815162342: I've updated my answer to use your suggestion.
@Шах please take note of update per comment above. This almost certainly will be implemented in the standard library before const generics are stabilised. These sorts of bumps are what you have to deal with when you choose to use unfinished, pre-release features.
|
-2

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.

2 Comments

Nightly version can compile constant generics but seems, there isn’t a way for an initialisation of String array.
@Fluffyeater The OP is aware that const generics are not yet available on stable, but is explicitly opting into them on nightly in order to see how they work and eventually provide feedback to developers. (This is how nightly is meant to be used.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.