Arrays e [T; N] em Rust

Guia completo de arrays em Rust: [T; N] com tamanho fixo, alocação na stack, indexação, iteração, map, from_fn, const generics e comparação com Vec e slices.

Um array em Rust, escrito como [T; N], é uma coleção de tamanho fixo com N elementos do tipo T. Diferente do Vec, o tamanho do array é conhecido em tempo de compilação e faz parte do seu tipo. Arrays são alocados na stack (quando declarados como variáveis locais), o que os torna extremamente rápidos para acessar e iterar.

Quando Usar Arrays

Use arrays quando:

  • O tamanho é conhecido em tempo de compilação e não muda.
  • Precisa de alocação na stack (sem heap) para máxima performance.
  • Trabalha com dados de tamanho fixo: coordenadas, pixels, buffers de tamanho conhecido.
  • Quer garantia em tempo de compilação de que o tamanho está correto.

Se o tamanho pode variar em tempo de execução, use Vec<T>. Para referências a sequências de tamanho variável, use slices (&[T]).

Criando e Inicializando

fn main() {
    // Tipo explícito: [T; N]
    let numeros: [i32; 5] = [1, 2, 3, 4, 5];

    // Tipo inferido pelo compilador
    let nomes = ["Ana", "Bruno", "Carla"];

    // Inicializar todos os elementos com o mesmo valor
    let zeros = [0u8; 1024]; // 1024 bytes, todos zero
    let tracos = ['-'; 40]; // 40 caracteres '-'

    // Usando std::array::from_fn para inicialização computada
    let quadrados: [i32; 10] = std::array::from_fn(|i| (i as i32 + 1).powi(2));
    println!("Quadrados: {:?}", quadrados);
    // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

    // Array de structs
    #[derive(Debug)]
    struct Ponto { x: f64, y: f64 }

    let pontos: [Ponto; 3] = [
        Ponto { x: 0.0, y: 0.0 },
        Ponto { x: 1.0, y: 2.0 },
        Ponto { x: 3.0, y: 4.0 },
    ];
    println!("{:?}", pontos);

    println!("Tamanho de numeros: {}", numeros.len());
    println!("Traços: {}", tracos.iter().collect::<String>());
}

Tabela de Métodos e Operações

OperaçãoDescriçãoComplexidade
arr[i]Acesso por índice (com verificação de limites)O(1)
arr.len()Número de elementosO(1)
arr.is_empty()Verifica se o array tem tamanho 0O(1)
arr.iter()Iterador sobre referências &TO(n)
arr.iter_mut()Iterador sobre referências mutáveis &mut TO(n)
arr.into_iter()Iterador que consome o array (move os elementos)O(n)
arr.map(f)Aplica função a cada elemento, retorna novo arrayO(n)
arr.each_ref()Converte &[T; N] em [&T; N]O(n)
arr.each_mut()Converte &mut [T; N] em [&mut T; N]O(n)
arr.contains(&v)Verifica se contém o valor (via slice)O(n)
arr.sort()Ordena in-place (via slice)O(n log n)
arr.reverse()Inverte a ordem (via slice)O(n)
arr.windows(n)Janelas deslizantes (via slice)O(n)
arr.chunks(n)Divide em pedaços (via slice)O(n)
std::array::from_fn(f)Cria array com função de índiceO(n)

Exemplos Práticos

1. Indexação e Iteração

fn main() {
    let meses = [
        "Janeiro", "Fevereiro", "Março", "Abril",
        "Maio", "Junho", "Julho", "Agosto",
        "Setembro", "Outubro", "Novembro", "Dezembro",
    ];

    // Acesso por índice
    println!("Primeiro mês: {}", meses[0]);
    println!("Último mês: {}", meses[11]);

    // Acesso seguro com get() (retorna Option)
    match meses.get(5) {
        Some(mes) => println!("Mês 6: {}", mes),
        None => println!("Índice inválido"),
    }

    // Iteração com enumerate
    println!("\nCalendário:");
    for (i, mes) in meses.iter().enumerate() {
        println!("  {:2}. {}", i + 1, mes);
    }

    // into_iter consome o array (move os elementos)
    let cores = ["vermelho", "verde", "azul"];
    for cor in cores {
        // Em Rust 2021+, `for x in array` usa IntoIterator
        println!("Cor: {}", cor);
    }
}

2. map e Transformações

fn main() {
    let temperaturas_c = [0.0_f64, 20.0, 37.0, 100.0];

    // map: transforma cada elemento, retornando novo array do mesmo tamanho
    let temperaturas_f = temperaturas_c.map(|c| c * 9.0 / 5.0 + 32.0);
    println!("Celsius:    {:?}", temperaturas_c);
    println!("Fahrenheit: {:?}", temperaturas_f);

    // Converter array de strings para array de tamanhos
    let palavras = ["Rust", "é", "incrível"];
    let tamanhos = palavras.map(|p| p.len());
    println!("Tamanhos: {:?}", tamanhos); // [4, 2, 9]

    // from_fn: criar array com lógica complexa
    let fibonacci: [u64; 10] = std::array::from_fn(|i| {
        if i < 2 {
            return i as u64;
        }
        let mut a = 0u64;
        let mut b = 1u64;
        for _ in 2..=i {
            let temp = a + b;
            a = b;
            b = temp;
        }
        b
    });
    println!("Fibonacci: {:?}", fibonacci);
    // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
}

3. Arrays como Slices e Métodos de Slice

fn main() {
    let mut numeros = [5, 3, 8, 1, 9, 2, 7, 4, 6];

    // Arrays implementam Deref<Target=[T]>, então métodos de slice funcionam
    println!("Contém 7? {}", numeros.contains(&7)); // true
    println!("Começa com [5, 3]? {}", numeros.starts_with(&[5, 3])); // true

    // Ordenar (modifica in-place via slice)
    numeros.sort();
    println!("Ordenado: {:?}", numeros); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

    // Reverter
    numeros.reverse();
    println!("Reverso: {:?}", numeros); // [9, 8, 7, 6, 5, 4, 3, 2, 1]

    // Janelas deslizantes
    let dados = [10, 20, 30, 40, 50];
    println!("\nJanelas de tamanho 3:");
    for janela in dados.windows(3) {
        let media: f64 = janela.iter().sum::<i32>() as f64 / 3.0;
        println!("  {:?} -> média = {:.1}", janela, media);
    }

    // Fatias (slices) de arrays
    let primeiros_tres = &dados[..3];
    let ultimos_dois = &dados[3..];
    println!("\nPrimeiros 3: {:?}", primeiros_tres);
    println!("Últimos 2: {:?}", ultimos_dois);
}

4. Const Generics — Funções Genéricas sobre Tamanho

// Função genérica que aceita arrays de qualquer tamanho
fn soma<const N: usize>(arr: &[f64; N]) -> f64 {
    arr.iter().sum()
}

fn media<const N: usize>(arr: &[f64; N]) -> f64 {
    soma(arr) / N as f64
}

fn imprimir_array<T: std::fmt::Debug, const N: usize>(arr: &[T; N]) {
    println!("[{}] {:?}", N, arr);
}

fn main() {
    let notas_3 = [8.5, 9.0, 7.5];
    let notas_5 = [8.0, 9.5, 7.0, 8.5, 6.5];

    // A mesma função funciona para arrays de tamanhos diferentes
    println!("Soma (3 notas): {:.1}", soma(&notas_3));
    println!("Média (3 notas): {:.2}", media(&notas_3));
    println!("Soma (5 notas): {:.1}", soma(&notas_5));
    println!("Média (5 notas): {:.2}", media(&notas_5));

    imprimir_array(&[1, 2, 3]);
    imprimir_array(&["a", "b", "c", "d"]);
}

5. Padrões Comuns com Arrays

fn main() {
    // Desestruturação
    let rgb = [255u8, 128, 0];
    let [r, g, b] = rgb;
    println!("R={}, G={}, B={}", r, g, b);

    // Comparação (arrays implementam PartialEq)
    let a = [1, 2, 3];
    let b = [1, 2, 3];
    let c = [1, 2, 4];
    println!("a == b? {}", a == b); // true
    println!("a == c? {}", a == c); // false

    // Array bidimensional (matriz)
    let matriz: [[f64; 3]; 3] = [
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
        [0.0, 0.0, 1.0],
    ];

    println!("\nMatriz identidade 3x3:");
    for linha in &matriz {
        println!("  {:?}", linha);
    }

    // Converter array em Vec
    let arr = [10, 20, 30];
    let vec: Vec<i32> = arr.into(); // ou arr.to_vec()
    println!("\nVec: {:?}", vec);

    // Tentar converter slice em array
    let slice: &[i32] = &[1, 2, 3];
    let arr: [i32; 3] = slice.try_into().expect("slice com tamanho errado");
    println!("Array de slice: {:?}", arr);

    // Usar array como buffer fixo
    let mut buffer = [0u8; 256];
    let mensagem = b"Ola, Rust!";
    buffer[..mensagem.len()].copy_from_slice(mensagem);
    println!(
        "Buffer: {}",
        std::str::from_utf8(&buffer[..mensagem.len()]).unwrap()
    );
}

Características de Desempenho

OperaçãoArray [T; N]Vec<T>Slice &[T]
AlocaçãoStackHeapReferência
Tamanho em runtimeFixo (compilação)DinâmicoDinâmico
Acesso por índiceO(1)O(1)O(1)
Overhead de memória0 bytes24 bytes (ptr+len+cap)16 bytes (ptr+len)
Cache-friendlySimSimSim
CrescimentoImpossívelpush() O(1) amort.Impossível

Memória: Arrays são alocados diretamente na stack frame da função (quando são variáveis locais). Isso significa zero overhead de alocação, mas cuidado: arrays muito grandes na stack podem causar stack overflow. Para arrays grandes (dezenas de KB ou mais), considere usar Vec ou Box<[T; N]>.

Tamanho do tipo: O tamanho de [T; N] é exatamente N * size_of::<T>() bytes. Por exemplo, [i32; 100] ocupa 400 bytes na stack.

Arrays vs Vec vs Slices

Critério[T; N]Vec<T>&[T]
TamanhoFixo (compilação)DinâmicoDinâmico
AlocaçãoStackHeapReferência
Pode crescerNaoSimNao
Tipo de ownershipOwnedOwnedBorrowed
Uso típicoBuffers fixos, coordsListas dinâmicasParâmetros de função
Coerção para slice&arr -> &[T]&vec -> &[T]Já é slice

Regra prática: Use arrays quando o tamanho é fixo e conhecido. Use Vec quando precisa de tamanho dinâmico. Use &[T] como parâmetro de função para aceitar tanto arrays quanto vetores.

// Função que aceita qualquer sequência (array, Vec, slice)
fn processar(dados: &[i32]) {
    println!("Processando {} elementos", dados.len());
}

fn main() {
    let array = [1, 2, 3];
    let vetor = vec![4, 5, 6];

    processar(&array);   // array -> &[i32]
    processar(&vetor);   // Vec -> &[i32]
    processar(&[7, 8]);  // slice literal
}

Veja Também