Como posso criar uma matriz de tamanho fixo de Strings usando genéricos constantes?

Aug 17 2020

Eu tenho uma função que usa uma constante genérica:

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
}

Como posso criar uma matriz de tamanho fixo de Strings no meu caso? let mut row: [String; S] = ["".to_string(), S];não funciona porque Stringnão implementa o Copytraço.

Respostas

4 eggyal Aug 17 2020 at 23:49

Você pode fazer isso com MaybeUninite 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!()
}

Claro, pode ser mais fácil abstrair tal lógica para sua própria característica:

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()
        }
    }
}

(A única razão para usar sua própria característica, em vez de Defaulté que as implementações da última entrariam em conflito com aquelas fornecidas na biblioteca padrão para matrizes de até 32 elementos; eu espero que a biblioteca padrão substitua sua implementação Defaultpor algo semelhante ao acima, uma vez que os genéricos const tenham estabilizado).

Nesse caso, você teria agora:

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

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

    // Some code

    todo!()
}

Veja no Playground .