Introdução
Bem-vindo(a) a Todos os Lugares Onde Padrões Podem Ser Usados. Este laboratório faz parte do Livro de Rust. Você pode praticar suas habilidades em Rust no LabEx.
Neste laboratório, exploraremos todos os lugares onde padrões podem ser usados em Rust.
Todos os Lugares Onde Padrões Podem Ser Usados
Padrões aparecem em vários lugares em Rust, e você os tem usado muito sem perceber! Esta seção discute todos os lugares onde padrões são válidos.
Ramos match
Como discutido no Capítulo 6, usamos padrões nos ramos das expressões match. Formalmente, as expressões match são definidas pela palavra-chave match, um valor para corresponder e um ou mais ramos de correspondência que consistem em um padrão e uma expressão para executar se o valor corresponder ao padrão desse ramo, assim:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
Por exemplo, aqui está a expressão match da Listagem 6-5 que corresponde a um valor Option<i32> na variável x:
match x {
None => None,
Some(i) => Some(i + 1),
}
Os padrões nesta expressão match são None e Some(i) à esquerda de cada seta.
Um requisito para as expressões match é que elas precisam ser exaustivas no sentido de que todas as possibilidades para o valor na expressão match devem ser consideradas. Uma maneira de garantir que você cobriu todas as possibilidades é ter um padrão genérico para o último ramo: por exemplo, um nome de variável correspondendo a qualquer valor nunca pode falhar e, portanto, cobre todos os casos restantes.
O padrão específico _ corresponderá a qualquer coisa, mas nunca se vincula a uma variável, por isso é frequentemente usado no último ramo match. O padrão _ pode ser útil quando você deseja ignorar qualquer valor não especificado, por exemplo. Abordaremos o padrão _ com mais detalhes em "Ignorando Valores em um Padrão".
Expressões condicionais if let
No Capítulo 6, discutimos como usar expressões if let principalmente como uma maneira mais curta de escrever o equivalente a um match que corresponde apenas a um caso. Opcionalmente, if let pode ter um else correspondente contendo código para executar se o padrão no if let não corresponder.
A Listagem 18-1 mostra que também é possível misturar e combinar expressões if let, else if e else if let. Fazer isso nos dá mais flexibilidade do que uma expressão match na qual podemos expressar apenas um valor para comparar com os padrões. Além disso, o Rust não exige que as condições em uma série de ramos if let, else if e else if let se relacionem entre si.
O código na Listagem 18-1 determina qual cor usar para o seu plano de fundo com base em uma série de verificações para várias condições. Para este exemplo, criamos variáveis com valores codificados que um programa real pode receber da entrada do usuário.
Nome do arquivo: src/main.rs
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
1 if let Some(color) = favorite_color {
2 println!(
"Using your favorite, {color}, as the background"
);
3 } else if is_tuesday {
4 println!("Tuesday is green day!");
5 } else if let Ok(age) = age {
6 if age > 30 {
7 println!("Using purple as the background color");
} else {
8 println!("Using orange as the background color");
}
9 } else {
10 println!("Using blue as the background color");
}
}
Listagem 18-1: Misturando if let, else if, else if let e else
Se o usuário especificar uma cor favorita [1], essa cor é usada como plano de fundo [2]. Se nenhuma cor favorita for especificada e hoje for terça-feira [3], a cor do plano de fundo é verde [4]. Caso contrário, se o usuário especificar sua idade como uma string e pudermos analisá-la com sucesso como um número [5], a cor será roxa [7] ou laranja [8], dependendo do valor do número [6]. Se nenhuma dessas condições se aplicar [9], a cor do plano de fundo será azul [10].
Essa estrutura condicional nos permite suportar requisitos complexos. Com os valores codificados que temos aqui, este exemplo imprimirá Using purple as the background color.
Você pode ver que if let também pode introduzir variáveis sombreadas da mesma forma que os ramos match podem: a linha if let Ok(age) = age [5] introduz uma nova variável age sombreada que contém o valor dentro da variante Ok. Isso significa que precisamos colocar a condição if age > 30 [6] dentro desse bloco: não podemos combinar essas duas condições em if let Ok(age) = age && age > 30. O age sombreado que queremos comparar com 30 não é válido até que o novo escopo comece com a chave.
A desvantagem de usar expressões if let é que o compilador não verifica a exaustividade, enquanto com expressões match ele o faz. Se omitíssemos o último bloco else [9] e, portanto, deixássemos de lidar com alguns casos, o compilador não nos alertaria sobre o possível bug de lógica.
Laços condicionais while let
Semelhante em construção ao if let, o laço condicional while let permite que um laço while seja executado enquanto um padrão continuar a corresponder. Na Listagem 18-2, codificamos um laço while let que usa um vetor como uma pilha e imprime os valores no vetor na ordem inversa em que foram inseridos.
Nome do arquivo: src/main.rs
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{top}");
}
Listagem 18-2: Usando um laço while let para imprimir valores enquanto stack.pop() retorna Some
Este exemplo imprime 3, 2 e depois 1. O método pop remove o último elemento do vetor e retorna Some(value). Se o vetor estiver vazio, pop retorna None. O laço while continua executando o código em seu bloco enquanto pop retorna Some. Quando pop retorna None, o laço para. Podemos usar while let para remover cada elemento de nossa pilha.
Laços for
Em um laço for, o valor que segue diretamente a palavra-chave for é um padrão. Por exemplo, em for x in y, o x é o padrão. A Listagem 18-3 demonstra como usar um padrão em um laço for para desestruturar (destructure), ou quebrar, uma tupla como parte do laço for.
Nome do arquivo: src/main.rs
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
Listagem 18-3: Usando um padrão em um laço for para desestruturar uma tupla
O código na Listagem 18-3 imprimirá o seguinte:
a is at index 0
b is at index 1
c is at index 2
Adaptamos um iterador usando o método enumerate para que ele produza um valor e o índice para esse valor, colocados em uma tupla. O primeiro valor produzido é a tupla (0, 'a'). Quando esse valor é correspondido ao padrão (index, value), index será 0 e value será 'a', imprimindo a primeira linha da saída.
Declarações let
Antes deste capítulo, discutimos explicitamente o uso de padrões com match e if let, mas, na verdade, usamos padrões em outros lugares também, incluindo em declarações let. Por exemplo, considere esta atribuição de variável direta com let:
let x = 5;
Toda vez que você usou uma declaração let como esta, você esteve usando padrões, embora possa não ter percebido! Mais formalmente, uma declaração let se parece com isto:
let PATTERN = EXPRESSION;
Em declarações como let x = 5; com um nome de variável no slot PATTERN, o nome da variável é apenas uma forma particularmente simples de um padrão. Rust compara a expressão com o padrão e atribui quaisquer nomes que encontrar. Então, no exemplo let x = 5;, x é um padrão que significa "vincular o que corresponde aqui à variável x". Como o nome x é o padrão inteiro, este padrão efetivamente significa "vincular tudo à variável x, seja qual for o valor".
Para ver o aspecto de correspondência de padrões de let mais claramente, considere a Listagem 18-4, que usa um padrão com let para desestruturar uma tupla.
let (x, y, z) = (1, 2, 3);
Listagem 18-4: Usando um padrão para desestruturar uma tupla e criar três variáveis de uma vez
Aqui, correspondemos uma tupla a um padrão. Rust compara o valor (1, 2, 3) ao padrão (x, y, z) e vê que o valor corresponde ao padrão, pois vê que o número de elementos é o mesmo em ambos, então Rust vincula 1 a x, 2 a y e 3 a z. Você pode pensar neste padrão de tupla como aninhando três padrões de variáveis individuais dentro dele.
Se o número de elementos no padrão não corresponder ao número de elementos na tupla, o tipo geral não corresponderá e obteremos um erro do compilador. Por exemplo, a Listagem 18-5 mostra uma tentativa de desestruturar uma tupla com três elementos em duas variáveis, o que não funcionará.
let (x, y) = (1, 2, 3);
Listagem 18-5: Construindo incorretamente um padrão cujas variáveis não correspondem ao número de elementos na tupla
Tentar compilar este código resulta neste erro de tipo:
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer},
{integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
Para corrigir o erro, poderíamos ignorar um ou mais dos valores na tupla usando _ ou .., como você verá em "Ignorando Valores em um Padrão". Se o problema é que temos muitas variáveis no padrão, a solução é fazer com que os tipos correspondam removendo variáveis para que o número de variáveis seja igual ao número de elementos na tupla.
Parâmetros de Função
Parâmetros de função também podem ser padrões. O código na Listagem 18-6, que declara uma função chamada foo que recebe um parâmetro chamado x do tipo i32, deve agora parecer familiar.
fn foo(x: i32) {
// code goes here
}
Listagem 18-6: Uma assinatura de função usando padrões nos parâmetros
A parte x é um padrão! Como fizemos com let, poderíamos corresponder uma tupla nos argumentos de uma função ao padrão. A Listagem 18-7 divide os valores em uma tupla ao passá-la para uma função.
Nome do arquivo: src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
Listagem 18-7: Uma função com parâmetros que desestruturam uma tupla
Este código imprime Current location: (3, 5). Os valores &(3, 5) correspondem ao padrão &(x, y), então x é o valor 3 e y é o valor 5.
Também podemos usar padrões em listas de parâmetros de closure da mesma forma que nas listas de parâmetros de função, porque closures são semelhantes a funções, como discutido no Capítulo 13.
Neste ponto, você viu várias maneiras de usar padrões, mas os padrões não funcionam da mesma forma em todos os lugares onde podemos usá-los. Em alguns lugares, os padrões devem ser irrefutáveis; em outras circunstâncias, eles podem ser refutáveis. Discutiremos esses dois conceitos a seguir.
Resumo
Parabéns! Você concluiu o laboratório "Todos os Lugares Onde os Padrões Podem Ser Usados". Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.