Slices em Rust
Um slice (fatia) permite pegar emprestada uma parte de um array ou vetor, uma espécie de “janela” para um trecho dele, sem copiar os dados e sem tomar o ownership. Se você já usou borrowing com um único valor, o slice é a mesma ideia aplicada a uma sequência de elementos.
Fatiando um array
Seção intitulada “Fatiando um array”fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // elementos nos índices 1, 2 e 3 println!("{:?}", middle);}Saída:
[20, 30, 40]1..4 é o mesmo tipo de intervalo que você viu nos laços for: começa no índice 1 e termina antes do índice 4. Ou seja, você recebe os índices 1, 2 e 3.
Atalhos para intervalos
Seção intitulada “Atalhos para intervalos”Alguns atalhos que aparecem bastante:
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // do início até o índice 3 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // do índice 2 até o fim -> [30, 40, 50] println!("{:?}", &numbers[..]); // tudo -> [10, 20, 30, 40, 50]}Slices de string
Seção intitulada “Slices de string”Na verdade, você já estava usando slices: &str é um slice de uma String (ou de outro texto)! É por isso que os dois tipos combinam tão bem:
fn main() { let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3]; println!("{first_word}"); // "The"}Deixamos a frase em inglês de propósito. Em uma string, os índices de [0..3] contam bytes, não letras, e letras acentuadas como “ã” ou “é” ocupam 2 bytes cada. Com um texto em português, o corte poderia cair no meio de uma letra, e o programa daria panic.
Por que usar slices em vez de copiar?
Seção intitulada “Por que usar slices em vez de copiar?”Dá para imaginar resolver isso copiando o trecho que você quer para um novo Vec ou uma nova String. Os slices evitam isso completamente: eles só apontam para uma parte dos dados que já existem, do mesmo jeito que uma referência (&) aponta para um único valor. Isso significa nenhuma alocação extra, nenhuma cópia, e as regras de borrowing que você já conhece da página de Borrowing continuam valendo.
Uma função que recebe um slice
Seção intitulada “Uma função que recebe um slice”Slices são especialmente úteis como parâmetros de função, porque aceitam tanto arrays quanto vetores:
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32] aceita um slice vindo tanto de um array quanto de um Vec: uma única função, e as duas chamadas funcionam.
Alterando dados por meio de um slice mutável
Seção intitulada “Alterando dados por meio de um slice mutável”Até aqui, nossos slices só leram dados. Um slice mutável, &mut [T], permite que uma função altere os itens que recebeu, mas só esses itens:
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // só os índices 1 e 2 println!("{:?}", scores);
double_all(&mut scores); // o vetor inteiro println!("{:?}", scores);}Saída:
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]A primeira chamada passa &mut scores[1..3], então double_all só enxerga os índices 1 e 2. Tudo o que está fora desse intervalo fica protegido dela. Dentro da função, o slice volta a começar no índice 0: para double_all, é só uma lista de dois números.
Isso é seguro por causa de uma regra que você vai conhecer direito na página de Borrowing: enquanto a função segura um slice &mut, nada mais pode usar esses itens, então duas partes do seu código nunca conseguem alterá-los ao mesmo tempo. O vetor também precisa ser declarado com mut, senão o Rust não deixa você pegá-lo emprestado de forma mutável.
Prática
Seção intitulada “Prática”1. Divida ao meio
Seção intitulada “1. Divida ao meio”Crie o vetor vec![3, 6, 9, 12, 15, 18]. Imprima a primeira metade e a segunda metade dele como dois slices separados. Dica: calcule let mid = numbers.len() / 2; e use ..mid e mid...
Ver solução
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("Primeira metade: {:?}", first_half); println!("Segunda metade: {:?}", second_half);}Saída:
Primeira metade: [3, 6, 9]Segunda metade: [12, 15, 18]Como mid é calculado a partir de len(), o código continua funcionando se você adicionar ou remover números. Com um tamanho ímpar, o item que sobra fica na segunda metade.
2. O maior de qualquer lista
Seção intitulada “2. O maior de qualquer lista”Escreva uma função largest(numbers: &[i32]) -> i32 que devolve o maior número. Chame-a com um array, com um vetor e com um slice só do primeiro item do vetor. Dica: comece com numbers[0] e percorra o resto, como você faria com um array.
Ver solução
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}Saída:
93510Percorrer um slice entrega uma referência para cada item (&i32), e não o número em si. *n quer dizer “o valor para o qual n aponta”, então *n > biggest compara dois números comuns. Esta versão dá panic com um slice vazio, porque numbers[0] não existe. Mais adiante você vai aprender a devolver um Option nesse caso.
- Um slice (
&[T]) pega emprestada uma parte de um array ou vetor sem copiá-la. 1..4seleciona os índices de 1 a 3; omita um dos lados do..para ir desde o início ou até o fim.&stré, ele mesmo, um slice de texto. Por isso strings e slices se comportam de forma tão parecida.- Prefira
&[T]como tipo de parâmetro quando quiser aceitar tanto arrays quanto vetores. - Um slice mutável (
&mut [T]) permite que uma função altere só os itens que recebeu.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. O que um slice permite fazer com uma parte de um array ou vetor?
2. Quais índices &numbers[1..4] inclui?
3. Por que &[i32] é uma boa escolha de tipo para um parâmetro de função?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- The Slice Type — The Rust Book (em inglês): chega aos slices passo a passo, com um exemplo de “primeira palavra de uma string”.
- Arrays and Slices — Rust by Example (em inglês): exemplos executáveis de arrays emprestados como slices.
slice— documentação da std (em inglês): os vários métodos que os slices têm, comofirst,split_at,containsesort.- Slice types — The Rust Reference (em inglês): a definição precisa, se você quiser saber exatamente o que
[T]e&[T]significam.