Estou procurando obter um número conhecido de insumos em ferrugem. Neste caso, estou considerando mãos de pôquer para o problema 54 do projeto Euler, onde cada linha é analisada em duas mãos de cinco cartas cada. Encontrei duas abordagens principais para esta situação, das quais não gosto.
Abordagem 1:
let mut buffer_1 = Vec::with_capacity(5);
for i in 0..5 {
buffer_1.push(i)
}
assert_eq!(buffer_1.len(), 5);
Abordagem 2:
let mut buffer_2 = [None, 5];
for i in 0..5 {
buffer_2[i as usize] = Some(i)
}
A abordagem 1 está na pilha, apesar de ter um tamanho conhecido em tempo de compilação, e a abordagem 2 me fornece valores opcionais onde eu sei que tudo é um arquivo Some
. O que eu gostaria idealmente é algo capaz de coletar alguma função ou colsure em um array ou similar. por exemplo
fn array_from_colsure<T>(length: usize, closure: fn() -> T) -> Option<[T; length]> {
// implementation
}
#[test]
fn array_from_closure_test() {
let a: [i32; 5] = array_from_colsure(5, || {for i in 0..5 {i}}).unwrap()
}
Esclarecimento: estou procurando algo com essa funcionalidade e não criá-lo do zero.
Como mostra a excelente resposta de Silvio Mayolo (editei um pouco a funcionalidade desejada desde a pergunta original), implementar minha sugestão exigirá uma quantidade enorme de unsafe
código (sem mencionar um enorme esforço para uma otimização tão pequena). Portanto, não é sensato fazê-lo para um pequeno número de projetos.
Você pode usar
std::array::from_fn
:Eu também ofereço
ArrayVec
as caixas arrayvec ou tinyvec que seriam semelhantes à sua primeira abordagem, mas não usariam uma alocação de heap.Primeiro, deixe-me apenas dizer que o que você está fazendo aqui é otimizar prematuramente o . Você está tentando economizar microssegundos às custas de escrever código legível e de fácil manutenção. A Abordagem 1 tem o tipo correto, nunca é realocada e é imediatamente óbvia para um leitor humano, portanto, você deve usar a Abordagem 1.
Entretanto, vamos fingir que estamos em um microprocessador. Estamos executando em alguma máquina incorporada em um hospital, cada byte conta e as alocações de heap são problemáticas.
MaybeUninit
pode ser usado para alocar espaço de pilha sem armazenar nada nele.Essa é uma afirmação bastante confusa. Estamos criando alguma memória não inicializada e, em seguida, afirmando que ela foi inicializada. Mas não é: não foi inicializado. Bem, o que
assume_init
realmente quer dizer é "esta memória é tão inicializada quanto necessário para o tipo" e, como o array contém apenasMaybeUninit
, não há problema em esse tipo não ser inicializado.Agora escrevemos alguns dados. Presumo que seu cálculo real seja mais sofisticado do que
i as i32
, mas neste exemplo funcionará bem.Agora temos uma matriz de valores talvez não inicializados que sabemos que foram inicializados. Como
MaybeUninit<T>
eT
têm a mesma representação de memória, podemos transmutar um no outro.transmute
não tem sobrecarga de tempo de execução, porque está literalmente apenas reinterpretando os bytes de um tipo nos bytes de outro. Aqui está todo o nosso bloco de código.A quantidade
unsafe
aqui deve assustar você. Isso deveria fazer você correr e chamar a polícia de Rust. Há uma razão pela qual não escrevemos código dessa maneira normalmente. Mas isso pode ser feito se você se encontrar naquela situação de um em um milhão, em que um punhado extra de bytes realmente vale tanta complexidade.