Slices (&[T]) em Rust

Guia completo de slices (&[T]) em Rust: criação, indexação, iteração, windows, chunks, split, binary_search, sort e padrões práticos.

O que são Slices

Um slice (&[T]) é uma referência a uma sequência contígua de elementos do tipo T. Ele não possui os dados — apenas aponta para eles. Internamente, um slice é composto por dois valores: um ponteiro para o primeiro elemento e um comprimento. Essa representação simples permite que slices referenciem partes de arrays, vetores, ou qualquer bloco de memória contíguo.

Slices são fundamentais em Rust porque permitem trabalhar com sequências de dados de forma genérica, sem se preocupar se a origem é um array [T; N], um Vec<T>, ou outra estrutura. Ao aceitar &[T] como parâmetro de função (em vez de &Vec<T>), seu código se torna mais flexível e idiomático. A versão mutável &mut [T] permite modificar os elementos sem realocar.


Criando Slices

fn main() {
    // A partir de um array
    let array = [10, 20, 30, 40, 50];
    let slice_completo: &[i32] = &array;         // Todo o array
    let parcial: &[i32] = &array[1..4];          // [20, 30, 40]
    let do_inicio: &[i32] = &array[..3];         // [10, 20, 30]
    let ate_final: &[i32] = &array[2..];         // [30, 40, 50]

    // A partir de um Vec
    let vec = vec![1, 2, 3, 4, 5];
    let slice_vec: &[i32] = &vec;                // Coerção automática
    let parte: &[i32] = &vec[1..3];              // [2, 3]

    // Slice mutável
    let mut dados = vec![5, 3, 1, 4, 2];
    let slice_mut: &mut [i32] = &mut dados;
    slice_mut.sort();
    println!("Ordenado: {slice_mut:?}"); // [1, 2, 3, 4, 5]

    // Slice vazio
    let vazio: &[i32] = &[];

    // A partir de uma String (slice de bytes)
    let texto = String::from("Rust");
    let bytes: &[u8] = texto.as_bytes();

    println!("{slice_completo:?} | {parcial:?} | {do_inicio:?} | {ate_final:?}");
    println!("{slice_vec:?} | {parte:?} | {vazio:?} | {bytes:?}");
}

Tabela de Métodos Principais

MétodoDescriçãoExemplo
len()Número de elementoss.len()5
is_empty()Verifica se está vazios.is_empty()
first()Primeiro elemento (Option<&T>)s.first()Some(&1)
last()Último elemento (Option<&T>)s.last()Some(&5)
get(i)Acesso seguro por índices.get(2)Some(&3)
contains(&val)Verifica se contém o valors.contains(&3)true
starts_with(sub)Verifica se começa com o subslices.starts_with(&[1, 2])
ends_with(sub)Verifica se termina com o subslices.ends_with(&[4, 5])
iter()Iterador de &Ts.iter()
iter_mut()Iterador de &mut Ts.iter_mut()
windows(n)Janelas deslizantes de tamanho ns.windows(3)
chunks(n)Pedaços de tamanho ns.chunks(2)
chunks_exact(n)Pedaços exatos (descarta resto)s.chunks_exact(3)
split(f)Divide onde f é trues.split(|x| *x == 0)
splitn(n, f)Divide em no máximo n partess.splitn(2, |x| *x == 0)
split_at(i)Divide em dois slices na posição is.split_at(3)
sort()Ordena in-place (requer &mut [T])s.sort()
sort_by(f)Ordena com comparadors.sort_by(|a, b| b.cmp(a))
sort_unstable()Ordena (pode ser mais rápido)s.sort_unstable()
binary_search(&val)Busca binária (requer slice ordenado)s.binary_search(&5)
reverse()Inverte in-places.reverse()
swap(i, j)Troca elementos nas posições i e js.swap(0, 2)
rotate_left(n)Rotaciona para a esquerdas.rotate_left(2)
fill(val)Preenche com um valors.fill(0)
copy_from_slice(src)Copia de outro slice (mesmo tamanho)dest.copy_from_slice(src)
to_vec()Cria um Vec<T> a partir do slices.to_vec()
concat()Concatena slices aninhados[&[1,2], &[3,4]].concat()
join(sep)Junta com separadorDisponível para &[&str] etc.

Exemplos Práticos

1. Processamento com windows e chunks

fn media_movel(dados: &[f64], janela: usize) -> Vec<f64> {
    dados
        .windows(janela)
        .map(|w| w.iter().sum::<f64>() / w.len() as f64)
        .collect()
}

fn processar_em_lotes(dados: &[i32], tamanho_lote: usize) {
    for (i, lote) in dados.chunks(tamanho_lote).enumerate() {
        let soma: i32 = lote.iter().sum();
        let media = soma as f64 / lote.len() as f64;
        println!("Lote {}: {:?} (média: {media:.1})", i + 1, lote);
    }
}

fn main() {
    let precos = vec![100.0, 105.0, 98.0, 110.0, 107.0, 115.0, 112.0];
    let medias = media_movel(&precos, 3);
    println!("Médias móveis (3): {medias:.1?}");

    println!();

    let dados: Vec<i32> = (1..=15).collect();
    processar_em_lotes(&dados, 4);
}

2. Busca Binária em Dados Ordenados

fn main() {
    let dados = vec![2, 5, 8, 12, 16, 23, 38, 56, 72, 91];

    // binary_search retorna Result<usize, usize>
    // Ok(indice) se encontrado, Err(indice_insercao) se não
    match dados.binary_search(&23) {
        Ok(pos) => println!("23 encontrado na posição {pos}"),     // posição 5
        Err(pos) => println!("23 não encontrado, inserir em {pos}"),
    }

    match dados.binary_search(&15) {
        Ok(pos) => println!("15 encontrado na posição {pos}"),
        Err(pos) => println!("15 não encontrado, inserir em {pos}"), // posição 4
    }

    // binary_search_by para tipos customizados
    let nomes = vec!["Ana", "Bruno", "Carlos", "Diana", "Eduardo"];
    match nomes.binary_search_by(|probe| probe.cmp(&"Carlos")) {
        Ok(pos) => println!("Carlos na posição {pos}"),
        Err(_) => println!("Não encontrado"),
    }

    // Inserir mantendo a ordem
    let mut ordenado = vec![1, 3, 5, 7, 9];
    let novo = 6;
    let pos = ordenado.binary_search(&novo).unwrap_or_else(|x| x);
    ordenado.insert(pos, novo);
    println!("Após inserir {novo}: {ordenado:?}"); // [1, 3, 5, 6, 7, 9]
}

3. split e Padrões de Divisão

fn main() {
    let dados = [1, 0, 2, 3, 0, 4, 5, 6, 0, 7];

    // split — divide onde o predicado é verdadeiro
    println!("Split por zeros:");
    for grupo in dados.split(|x| *x == 0) {
        println!("  {grupo:?}");
    }
    // [1], [2, 3], [4, 5, 6], [7]

    // splitn — limita o número de divisões
    println!("\nSplit (máx 2 partes):");
    for grupo in dados.splitn(2, |x| *x == 0) {
        println!("  {grupo:?}");
    }
    // [1], [2, 3, 0, 4, 5, 6, 0, 7]

    // split_at — divide em posição específica
    let numeros = [10, 20, 30, 40, 50];
    let (esquerda, direita) = numeros.split_at(3);
    println!("\nEsquerda: {esquerda:?}"); // [10, 20, 30]
    println!("Direita: {direita:?}");     // [40, 50]

    // rsplit — divide da direita para a esquerda
    println!("\nRsplit por zeros:");
    for grupo in dados.rsplit(|x| *x == 0) {
        println!("  {grupo:?}");
    }
    // [7], [4, 5, 6], [2, 3], [1]
}

4. Funções Genéricas que Aceitam Slices

fn encontrar_maximo<T: PartialOrd>(slice: &[T]) -> Option<&T> {
    slice.iter().reduce(|max, x| if x > max { x } else { max })
}

fn estatisticas(dados: &[f64]) -> Option<(f64, f64, f64)> {
    if dados.is_empty() {
        return None;
    }

    let soma: f64 = dados.iter().sum();
    let media = soma / dados.len() as f64;

    let variancia = dados.iter()
        .map(|x| (x - media).powi(2))
        .sum::<f64>() / dados.len() as f64;

    let desvio_padrao = variancia.sqrt();

    Some((media, variancia, desvio_padrao))
}

fn imprimir_tabela(cabecalho: &[&str], dados: &[&[&str]]) {
    // Calcular largura de cada coluna
    let larguras: Vec<usize> = (0..cabecalho.len())
        .map(|col| {
            let max_dados = dados.iter()
                .filter_map(|linha| linha.get(col))
                .map(|s| s.len())
                .max()
                .unwrap_or(0);
            cabecalho[col].len().max(max_dados)
        })
        .collect();

    // Imprimir cabeçalho
    for (i, h) in cabecalho.iter().enumerate() {
        print!("| {:<width$} ", h, width = larguras[i]);
    }
    println!("|");

    // Separador
    for w in &larguras {
        print!("|{}", "-".repeat(w + 2));
    }
    println!("|");

    // Dados
    for linha in dados {
        for (i, celula) in linha.iter().enumerate() {
            print!("| {:<width$} ", celula, width = larguras[i]);
        }
        println!("|");
    }
}

fn main() {
    // Aceita Vec, array ou slice — todos coercem para &[T]
    let vec = vec![3.2, 1.8, 4.5, 2.1, 3.7];
    let array = [10.0, 20.0, 30.0];

    println!("Max vec: {:?}", encontrar_maximo(&vec));    // Some(4.5)
    println!("Max array: {:?}", encontrar_maximo(&array)); // Some(30.0)

    if let Some((media, var, dp)) = estatisticas(&vec) {
        println!("Média: {media:.2}, Variância: {var:.2}, DP: {dp:.2}");
    }

    println!();

    let cabecalho = ["Nome", "Linguagem", "Nível"];
    let dados: Vec<&[&str]> = vec![
        &["Ana", "Rust", "Avançado"],
        &["Bruno", "Python", "Intermediário"],
        &["Carlos", "Go", "Iniciante"],
    ];
    imprimir_tabela(&cabecalho, &dados);
}

5. Operações In-Place com Slices Mutáveis

fn particionar<T: PartialOrd + Copy>(slice: &mut [T], pivot: T) -> usize {
    let mut i = 0;
    for j in 0..slice.len() {
        if slice[j] <= pivot {
            slice.swap(i, j);
            i += 1;
        }
    }
    i
}

fn main() {
    // sort_unstable — geralmente mais rápido que sort
    let mut numeros = vec![38, 27, 43, 3, 9, 82, 10];
    numeros.sort_unstable();
    println!("Ordenado: {numeros:?}");

    // sort_by_key — ordenar por critério
    let mut palavras = vec!["banana", "uva", "abacaxi", "kiwi", "maçã"];
    palavras.sort_by_key(|p| p.len());
    println!("Por tamanho: {palavras:?}");

    // reverse
    let mut seq = vec![1, 2, 3, 4, 5];
    seq.reverse();
    println!("Invertido: {seq:?}"); // [5, 4, 3, 2, 1]

    // rotate_left / rotate_right
    let mut fila = vec!["A", "B", "C", "D", "E"];
    fila.rotate_left(2);
    println!("Rotação esquerda: {fila:?}"); // ["C", "D", "E", "A", "B"]

    // fill — preencher com um valor
    let mut buffer = vec![0u8; 10];
    buffer[3..7].fill(0xFF);
    println!("Buffer: {buffer:?}");

    // swap
    let mut dados = vec![10, 20, 30, 40, 50];
    dados.swap(1, 3);
    println!("Após swap: {dados:?}"); // [10, 40, 30, 20, 50]

    // Particionar
    let mut vals = vec![7, 2, 9, 1, 5, 8, 3, 6, 4];
    let pos = particionar(&mut vals, 5);
    println!("Particionado (pivot=5): {vals:?}, posição: {pos}");
}

Dicas de Performance e Armadilhas

  1. Prefira &[T] a &Vec<T> em parâmetros: Aceitar &[T] torna sua função mais genérica — ela funciona com arrays, vetores e outros slices. Vec<T> implementa Deref<Target=[T]>, então a conversão é automática.

  2. Indexação pode causar panic: slice[i] gera panic se i >= slice.len(). Use slice.get(i) para acesso seguro com Option<&T>.

  3. sort_unstable vs sort: sort_unstable() é geralmente mais rápido e usa menos memória que sort(), mas não preserva a ordem de elementos iguais. Use sort() quando a estabilidade importa.

  4. chunks_exact descarta o resto: Se o tamanho do slice não é múltiplo do tamanho do chunk, chunks_exact ignora os elementos restantes. Use chunks_exact(n).remainder() para acessar o que sobrou.

  5. Copy semântico com slices: Slices são referências e, portanto, Copy. Passar &[T] é barato independente do tamanho — apenas um ponteiro e um comprimento são copiados.

  6. split_at para processamento paralelo: Dividir um slice mutável em duas metades com split_at_mut permite processá-las em paralelo sem violar as regras de borrowing.


Veja Também