O primeiro tipo de coleção que veremos é Vec<T>
, também conhecido como vetor. Os vetores permitem que você armazene mais de um valor em uma única estrutura de dados que coloca todos os valores próximos uns dos outros na memória. Os vetores só podem armazenar valores do mesmo tipo. Eles são úteis quando você tem uma lista de itens, como as linhas de texto em um arquivo ou os preços dos itens em um carrinho de compras.
Criação de um novo vetor
Para criar um novo vetor vazio, podemos chamar a função Vec::new
, conforme mostrado na Listagem 8-1.
Observe que adicionamos uma anotação de tipo aqui. Como não estamos inserindo nenhum valor neste vetor, Rust não sabe que tipo de elementos pretendemos armazenar. esse é um ponto importante. Os vetores são implementados usando genéricos; vamos cobrir como usar genéricos com seus próprios tipos no Capítulo 10. Por agora, saiba que o tipo Vec<T>
fornecido pela biblioteca padrão pode conter qualquer tipo, e quando um vetor específico contém um tipo específico, o tipo é especificado entre colchetes angulares. Na Listagem 8-1, dissemos a Rust que o Vec<T>
em v
conterá elementos do tipo i32
.
Em um código mais realista, Rust pode frequentemente inferir o tipo de valor que você deseja armazenar, uma vez que você insere valores, portanto, raramente você precisa fazer essa anotação de tipo. É mais comum criar um Vec<T>
que tenha valores iniciais, e Rust fornece a macro vec!
por conveniência. A macro criará um novo vetor que contém os valores que você fornecer. Listagem 8-2 cria um novo Vec<i32>
que contém os valores 1
, 2
e 3
. O tipo inteiro é i32
porque esse é o tipo inteiro padrão, conforme discutimos na seção “Tipos de dados” do Capítulo 3.
Como fornecemos valores i32
iniciais, Rust pode inferir que o tipo de v
é Vec<i32>
e a anotação de tipo não é necessária. A seguir, veremos como modificar um vetor.
Atualizando um vetor
Para criar um vetor e, em seguida, adicionar elementos a ele, podemos usar o método push
, conforme mostrado na Listagem 8-3.
Como acontece com qualquer variável, se quisermos ser capazes de alterar seu valor, precisamos torná-la mutável usando a palavra-chave mut
, conforme discutido no Capítulo 3. Os números que colocamos dentro são todos do tipo i32
, e Rust infere isso a partir dos dados, portanto, não precisamos da anotação Vec<i32>
.
A eliminação de um vetor elimina seus elementos
Como qualquer outra struct
, um vetor é liberado quando sai do escopo, conforme anotado na Listagem 8-4.
Quando o vetor é eliminado, todo o seu conteúdo também é eliminado, o que significa que os inteiros que ele contém serão apagados. Isso pode parecer um ponto direto, mas pode se tornar um pouco mais complicado quando você começa a introduzir referências aos elementos do vetor. Vamos tratar disso a seguir!
Lendo Elementos de Vetores
Agora que você sabe como criar, atualizar e destruir vetores, saber como ler seu conteúdo é um bom próximo passo. Existem duas maneiras de fazer referência a um valor armazenado em um vetor. Nos exemplos, anotamos os tipos de valores que são retornados dessas funções para maior clareza.
A Listagem 8-5 mostra os dois métodos de acesso a um valor em um vetor, seja com a sintaxe de indexação ou o método get
.
Observe dois detalhes aqui. Primeiro, usamos o valor de índice de 2
para obter o terceiro elemento: os vetores são indexados por número, começando em zero. Em segundo lugar, as duas maneiras de obter o terceiro elemento são usando &
e []
, que nos dá uma referência, ou usando o método get
com o índice passado como um argumento, que nos dá um Option<&T>
.
Rust possui duas maneiras de referenciar um elemento, de modo que você pode escolher como o programa se comporta ao tentar usar um valor de índice para o qual o vetor não possui um elemento. Como exemplo, vamos ver o que um programa fará se tiver um vetor que contém cinco elementos e, em seguida, tenta acessar um elemento no índice 100, conforme mostrado na Listagem 8-6.
Este código entra em pânico!
Quando executamos este código, o primeiro método []
fará com que o programa entre em pânico porque faz referência a um elemento inexistente. Este método é melhor usado quando você deseja que seu programa trave se houver uma tentativa de acessar um elemento além do final do vetor.
Quando o método get
recebe um índice que está fora do vetor, ele retorna None
sem entrar em pânico. Você usaria este método se o acesso a um elemento além do intervalo do vetor acontecesse ocasionalmente em circunstâncias normais. Seu código terá lógica para lidar com Some(&element)
ou None
, conforme discutido no Capítulo 6. Por exemplo, o índice pode vir de uma pessoa que está inserindo um número. Se eles inserirem acidentalmente um número muito grande e o programa obtiver um valor None
, você pode informar ao usuário quantos itens estão no vetor atual e dar a ele outra chance de inserir um valor válido. Isso seria mais amigável do que travar o programa devido a um erro de digitação!
Quando o programa tem uma referência válida, o verificador de empréstimo impõe as regras de propriedade e empréstimo (abordadas no Capítulo 4) para garantir que essa referência e quaisquer outras referências ao conteúdo do vetor permaneçam válidas. Lembre-se da regra que afirma que você não pode ter referências mutáveis e imutáveis no mesmo escopo. Essa regra se aplica na Listagem 8-7, onde mantemos uma referência imutável ao primeiro elemento em um vetor e tentamos adicionar um elemento ao final, o que não funcionará se também tentarmos nos referir a esse elemento posteriormente na função:
Esse código não compila.
Compilar este código resultará neste erro:
$ cargo run
Compiling collections v0.1.0 (file:///projects/collections)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:6:5
|
4 | let first = &v[0];
| - immutable borrow occurs here
5 |
6 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
7 |
8 | println!("O primeiro elemento é: {}", first);
| ----- immutable borrow later used here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0502`.
error: could not compile `collections`
To learn more, run the command again with --verbose.
O código na Listagem 8-7 pode parecer que deve funcionar: por que uma referência ao primeiro elemento deveria se preocupar com quais mudanças no final do vetor? Este erro é devido à forma como os vetores funcionam: adicionar um novo elemento no final do vetor pode exigir a alocação de nova memória e a cópia dos elementos antigos para o novo espaço, se não houver espaço suficiente para colocar todos os elementos próximos a cada um outro onde o vetor está atualmente. Nesse caso, a referência ao primeiro elemento estaria apontando para a memória desalocada. As regras de empréstimo evitam que os programas terminem nessa situação.
Nota: Para obter mais detalhes sobre a implementação do tipo
Vec<T>
, consulte "The Rustonomicon".
Iterando sobre os valores em um vetor
Se quisermos acessar cada elemento em um vetor, por sua vez, podemos iterar por todos os elementos em vez de usar índices para acessar um de cada vez. A Listagem 8-8 mostra como usar um for
loop para obter referências imutáveis para cada elemento em um vetor de i32
valores e imprimi-los.
Também podemos iterar referências mutáveis para cada elemento em um vetor mutável para fazer alterações em todos os elementos. O loop for
na Listagem 8-9 será adicionado 50
a cada elemento.
Para alterar o valor ao qual a referência mutável se refere, temos que usar o operador dereference (*
) para obter o valor em i
antes de podermos usar o operador +=
. Falaremos mais sobre o operador de desreferenciação na seção “Seguindo o ponteiro para o valor com o operador de desreferenciação” do Capítulo 15.
Usando um Enum para armazenar vários tipos
No início deste capítulo, dissemos que os vetores só podem armazenar valores do mesmo tipo. Isso pode ser inconveniente; definitivamente há casos de uso para a necessidade de armazenar uma lista de itens de diferentes tipos. Felizmente, as variantes de um enum são definidas no mesmo tipo de enum, portanto, quando precisamos armazenar elementos de um tipo diferente em um vetor, podemos definir e usar um enum!
Por exemplo, digamos que queremos obter valores de uma linha em uma planilha na qual algumas das colunas da linha contenham inteiros, alguns números de ponto flutuante e algumas strings. Podemos definir um enum cujas variantes conterão os diferentes tipos de valor e, em seguida, todas as variantes do enum serão consideradas do mesmo tipo: o do enum. Então, podemos criar um vetor que contém esse enum e, portanto, em última análise, contém diferentes tipos. Demonstramos isso na Listagem 8-10.
O Rust precisa saber quais tipos estarão no vetor no momento da compilação para saber exatamente quanta memória no heap será necessária para armazenar cada elemento. Uma vantagem secundária é que podemos ser explícitos sobre quais tipos são permitidos neste vetor. Se Rust permitisse que um vetor contivesse qualquer tipo, haveria uma chance de que um ou mais dos tipos causassem erros nas operações realizadas nos elementos do vetor. Usar um enum mais uma expressão match
significa que o Rust garantirá no momento da compilação que todos os casos possíveis sejam tratados, conforme discutido no Capítulo 6.
Quando você está escrevendo um programa, se você não conhece o conjunto exaustivo de tipos que o programa obterá em tempo de execução para armazenar em um vetor, a técnica enum não funcionará. Em vez disso, você pode usar um objeto de característica, que abordaremos no Capítulo 17.
Agora que discutimos algumas das maneiras mais comuns de usar vetores, certifique-se de revisar a documentação da API para todos os muitos métodos úteis definidos Vec<T>
pela biblioteca padrão. Por exemplo, além de push
, um método pop
remove e retorna o último elemento. Vamos passar para o próximo tipo de coleção: String
!
Traduzido por Acervo Lima. O original pode ser acessado aqui.
0 comentários:
Postar um comentário