Slice: referências para partes de coleções
1. O que é um Slice e por que ele existe?
Um slice (ou "fatia") é uma referência para uma sequência contígua de elementos dentro de uma coleção, sem possuir a propriedade (ownership) dos dados. Diferente de um vetor ou array que detém seus elementos, um slice apenas "empresta" uma visão parcial ou total de uma coleção existente.
A diferença fundamental é que o slice não aloca nem gerencia memória — ele apenas aponta para um bloco de dados que pertence a outra variável. Pense em um slice como uma janela: você pode ver e interagir com o conteúdo, mas não pode mover a janela para outro lugar sem permissão.
Essa abstração é poderosa porque permite que funções operem sobre partes de coleções sem precisar copiar dados, economizando memória e tempo de processamento.
2. Slice de Arrays e Vetores: &[T]
A sintaxe básica para criar slices usa intervalos (ranges): &colecao[inicio..fim]. O intervalo é exclusivo no final.
let v = vec![1, 2, 3, 4, 5];
let slice = &v[1..4]; // elementos 2, 3, 4
println!("Slice: {:?}", slice); // [2, 3, 4]
Existem variações úteis:
- &v[..3] — do início até o índice 3 (exclusivo)
- &v[2..] — do índice 2 até o final
- &v[..] — o vetor inteiro como slice
let v = vec![10, 20, 30, 40, 50];
let inicio = &v[..2]; // [10, 20]
let fim = &v[3..]; // [40, 50]
let completo = &v[..]; // [10, 20, 30, 40, 50]
println!("Início: {:?}", inicio);
println!("Fim: {:?}", fim);
println!("Completo: {:?}", completo);
O tipo resultante é &[i32] — uma referência imutável para uma fatia do vetor original. Como o slice toma emprestado os dados, as regras de borrowing se aplicam: enquanto o slice existir, você não pode modificar o vetor original de forma que invalide a referência.
3. Slice de Strings: &str como um caso especial
Em Rust, &str é essencialmente um slice de uma String (ou de uma string literal). A relação entre String e &str é análoga à relação entre Vec<T> e &[T].
let s = String::from("hello");
let slice = &s[0..2]; // "he"
println!("Slice: {}", slice);
Cuidado com índices de bytes: strings em Rust são codificadas em UTF-8, e cada caractere pode ocupar mais de um byte. Índices incorretos podem causar panic em tempo de execução.
let texto = String::from("Olá, mundo!");
// Erro! O caractere 'á' ocupa 2 bytes, então índice 3 está no meio dele
// let slice_invalido = &texto[0..3]; // PANIC!
// Correto: usar índices que respeitam limites de caracteres
let slice_valido = &texto[0..4]; // "Olá"
println!("Slice válido: {}", slice_valido);
&str vs String: Use &str para parâmetros de funções que só precisam ler strings (mais flexível e eficiente). Use String quando precisar modificar ou possuir a string.
4. Slice Mútavel: &mut [T]
Slices mutáveis permitem modificar os elementos da coleção original através da referência.
let mut vetor = vec![1, 2, 3, 4, 5];
let slice_mut = &mut vetor[1..4]; // referência mutável para [2, 3, 4]
slice_mut[0] = 99; // modifica o elemento no índice 1 do vetor original
slice_mut[2] = 77; // modifica o elemento no índice 3 do vetor original
println!("Vetor modificado: {:?}", vetor); // [1, 99, 3, 77, 5]
Restrições de borrowing: Não é possível ter um slice mutável e outro imutável ao mesmo tempo.
let mut v = vec![1, 2, 3];
let s1 = &v[..]; // borrow imutável
// let s2 = &mut v[..]; // ERRO! já existe um borrow imutável
Slices mutáveis não funcionam com strings devido à codificação UTF-8 — modificar bytes individuais poderia quebrar a validade da string.
5. Operações e Métodos Comuns em Slices
Slices oferecem diversos métodos úteis para manipulação e consulta:
let slice = &[10, 20, 30, 40, 50];
// Acessando elementos com segurança
println!("Primeiro: {:?}", slice.first()); // Some(10)
println!("Último: {:?}", slice.last()); // Some(50)
println!("Índice 2: {:?}", slice.get(2)); // Some(30)
println!("Índice 10: {:?}", slice.get(10)); // None
// Propriedades
println!("Tamanho: {}", slice.len()); // 5
println!("Está vazio?: {}", slice.is_empty()); // false
// Iteração
for elem in slice {
print!("{} ", elem); // 10 20 30 40 50
}
// Sub-fatiamento
let sub_slice = &slice[1..3]; // [20, 30]
println!("\nSub-slice: {:?}", sub_slice);
// Busca
println!("Contém 30?: {}", slice.contains(&30)); // true
// Ordenação (requer slice mutável)
let mut dados = vec![5, 3, 1, 4, 2];
dados.sort();
println!("Ordenado: {:?}", dados); // [1, 2, 3, 4, 5]
6. Slices como Parâmetros de Funções
Usar &[T] como parâmetro é mais flexível que &Vec<T>, pois aceita arrays, vetores e outros slices.
fn sum(slice: &[i32]) -> i32 {
slice.iter().sum()
}
// Funciona com vetor
let vec = vec![1, 2, 3];
println!("Soma do vetor: {}", sum(&vec));
// Funciona com array
let arr = [4, 5, 6];
println!("Soma do array: {}", sum(&arr));
// Funciona com slice literal
println!("Soma do literal: {}", sum(&[7, 8, 9]));
// Funciona com sub-slice
println!("Soma do sub-slice: {}", sum(&vec[0..2]));
Isso funciona devido à coerção de tipos via trait Deref: &Vec<T> e &[T; N] são convertidos automaticamente para &[T]. A boa prática é preferir &[T] em parâmetros de funções que só precisam ler dados sequenciais.
7. Casos Especiais e Armadilhas Comuns
Slice vazio: Útil como valor padrão ou para representar "nenhum elemento".
let vazio: &[i32] = &[];
println!("Slice vazio: {:?}", vazio); // []
Slice de um array fixo: Arrays podem ser convertidos em slices completos.
let arr = [1, 2, 3];
let s: &[i32] = &arr[..];
println!("Array como slice: {:?}", s);
Erro comum: Tentar criar slice mutável de string não compila.
let mut s = String::from("teste");
// let slice_mut = &mut s[0..2]; // ERRO! não é possível
Panic com índices fora dos limites: Sempre prefira slice.get() para acesso seguro.
let slice = &[1, 2, 3];
// let val = slice[10]; // PANIC! índice fora dos limites
match slice.get(10) {
Some(val) => println!("Valor: {}", val),
None => println!("Índice inválido"),
}
Slices são uma das abstrações mais elegantes de Rust, combinando segurança de memória com eficiência. Dominá-los é essencial para escrever código idiomático e performático.
Referências
-
The Rust Programming Language - Chapter 4.3: The Slice Type — Capítulo oficial do livro que introduz slices de forma abrangente, com exemplos práticos de arrays, vetores e strings.
-
Rust by Example - Slices — Tutorial interativo com exemplos comentados sobre criação e uso de slices em Rust.
-
Rust Reference - Slice Types — Documentação técnica detalhada sobre a representação interna e o comportamento dos tipos slice na linguagem.
-
Rust std::slice documentation — Documentação completa da biblioteca padrão para slices, incluindo todos os métodos disponíveis e exemplos de uso.
-
Rust Strings and Slices - A Practical Guide — Artigo técnico que explora a relação entre strings e slices, com dicas práticas para evitar armadilhas comuns.