O Rust tem um operador de fluxo de controle extremamente poderoso, chamado, match
que permite comparar um valor com uma série de padrões e, em seguida, executar o código com base nos padrões correspondentes. Os padrões podem ser compostos de valores literais, nomes de variáveis, curingas e muitas outras coisas; O Capítulo 18 cobre todos os diferentes tipos de padrões e o que eles fazem. O poder de match
vem da expressividade dos padrões e do fato de que o compilador confirma que todos os casos possíveis foram tratados.
Pense em uma expressão match
como uma máquina de separar moedas: as moedas deslizam por uma trilha com orifícios de vários tamanhos ao longo dela, e cada moeda cai pelo primeiro orifício que encontra em que se encaixa. Da mesma forma, os valores passam por cada padrão em match
, e no primeiro padrão o valor “se ajusta”, o valor cai no bloco de código associado a ser usado durante a execução.
Como acabamos de mencionar as moedas, vamos usá-las como exemplo usando match
! Podemos escrever uma função que pode pegar uma moeda desconhecida dos Estados Unidos e, de maneira semelhante à máquina de contagem, determinar qual moeda ela é e retornar seu valor em centavos, conforme mostrado aqui na Listagem 6-3.
Vamos decompôr match
na função valor_em_centavos
. Primeiro, listamos a palavra-chave match
seguida por uma expressão, que neste caso é o valor moeda
. Isso parece muito semelhante a uma expressão usada com if
, mas há uma grande diferença: com if
, a expressão precisa retornar um valor booleano, mas aqui pode ser de qualquer tipo. O tipo de moeda
neste exemplo é o enum Moeda
que definimos na linha 1.
Em seguida estão os braços de match
. Um braço tem duas partes: um padrão e algum código. O primeiro braço aqui tem um padrão que é o valor Moeda::Penny
e, em seguida, o operador =>
que separa o padrão e o código a ser executado. O código, neste caso, é apenas o valor 1
. Cada braço é separado do próximo por uma vírgula.
Quando a expressão match
é executada, ela compara o valor resultante com o padrão de cada braço, em ordem. Se um padrão corresponder ao valor, o código associado a esse padrão será executado. Se esse padrão não corresponder ao valor, a execução continua para o próximo braço, da mesma forma que em uma máquina de classificação de moedas. Podemos ter quantos braços precisarmos: na Listagem 6-3, nossa expressão match
tem quatro braços.
O código associado a cada braço é uma expressão e o valor resultante da expressão no braço correspondente é o valor que é retornado para a expressão match
inteira.
Os colchetes normalmente não são usados se o código do braço de correspondência for curto, como na Listagem 6-3, onde cada braço apenas retorna um valor. Se você deseja executar várias linhas de código em um braço de correspondência, pode usar chaves. Por exemplo, o código a seguir imprimiria “Lucky penny!” toda vez que o método foi chamado com um, Moeda::Penny
mas ainda assim retornaria o último valor do bloco 1
:
Padrões que se ligam a valores
Outro recurso útil dos braços de combinação é que eles podem se vincular às partes dos valores que correspondem ao padrão. É assim que podemos extrair valores de variantes enum.
Como exemplo, vamos alterar uma de nossas variantes de enum para conter os dados dentro dela. De 1999 a 2008, os Estados Unidos cunharam quartos com designs diferentes para cada um dos 50 estados de um lado. Nenhuma outra moeda tem design de estado, então apenas quartos têm esse valor extra. Podemos adicionar essas informações ao nosso enum
alterando a variante Quarter
para incluir um valor UsState
armazenado dentro dela, o que fizemos aqui na Listagem 6-4.
Vamos imaginar que um amigo nosso está tentando coletar todos os 50 bairros estaduais. Enquanto classificamos nosso troco por tipo de moeda, também chamaremos o nome do estado associado a cada trimestre para que, se for um que nosso amigo não tenha, eles possam adicioná-lo à sua coleção.
Na expressão de correspondência para este código, adicionamos uma variável chamada state
ao padrão que corresponde aos valores da variante Moeda::Quarter
. Quando um Moeda::Quarter
corresponde, a variável state
se vincula ao valor do estado daquele trimestre. Então, podemos usar state
no código para esse braço, assim:
Se tivéssemos que chamar valor_em_centavos(Moeda::Quarter(UsState::Alaska))
, moeda
seria Moeda::Quarter(UsState::Alaska)
. Quando comparamos esse valor com cada um dos braços do jogo, nenhum deles combina até alcançarmos Moeda::Quarter(state)
. Nesse ponto, a vinculação para state
será o valor UsState::Alaska
. Podemos então usar essa ligação na expressão println!
, obtendo assim o valor do estado interno da variante enum Moeda
para Quarter
.
Combinando com Option<T>
Na seção anterior, queríamos obter o valor T
interno de Some
ao usar Option<T>
; também podemos lidar com Option<T>
usando match
como fizemos com o enum Moeda
! Em vez de comparar moedas, compararemos as variantes de Option<T>
, mas a forma como a expressão match
funciona permanece a mesma.
Digamos que queremos escrever uma função que recebe um Option<i32>
e, se houver um valor dentro, adicione 1 a esse valor. Se não houver um valor dentro, a função deve retornar o valor None
e não tentar realizar nenhuma operação.
Esta função é muito fácil de escrever, graças a match
, e será semelhante à Listagem 6-5.
Vamos examinar a primeira execução de mais_um
com mais detalhes. Quando chamamos mais_um(cinco)
, a variável x
no corpo de mais_um
terá o valor Some(5)
. Em seguida, comparamos isso com cada braço de jogo.
O valor Some(5)
não corresponde ao padrão None
, então continuamos para o próximo braço.
Some(5)
corresponder a Some(i)
? Sim, é verdade! Temos a mesma variante. O i
vincula-se ao valor contido em Some
, portanto, i
assume o valor 5
. O código no braço de correspondência é então executado, então adicionamos 1 ao valor de i
e criamos um novo valor Some
com nosso total interno 6
.
Agora vamos considerar a segunda chamada de mais_um
na Listagem 6-5, onde x
é None
. Entramos no match
e comparamos com o primeiro braço.
Corresponde! Não há valor a ser adicionado, então o programa para e retorna o valor None
no lado direito de =>
. Como o primeiro braço combinou, nenhum outro braço é comparado.
Combinar match
e enums é útil em muitas situações. Você verá muito esse padrão no código Rust: match
em um enum, vincule uma variável aos dados internos e execute o código com base nele. É um pouco complicado no início, mas quando você se acostumar, vai desejar tê-lo em todas linguagens. É sempre um favorito do usuário.
As partidas são exaustivas
Há um outro aspecto de match
que precisamos discutir. Considere esta versão de nossa função mais_um
que tem um bug e não compila:
Esse código não compila.
Não tratamos do caso None
, então este código causará um bug. Felizmente, é um bug que Rust sabe como detectar. Se tentarmos compilar este código, obteremos este erro:
$ cargo run
Compiling enums v0.1.0 (file:///projects/enums)
error[E0004]: non-exhaustive patterns: `None` not covered
--> src/main.rs:3:15
|
3 | match x {
| ^ pattern `None` not covered
|
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
= note: the matched value is of type `Option<i32>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0004`.
error: could not compile `enums`
To learn more, run the command again with --verbose.
Rust sabe que não cobrimos todos os casos possíveis e até sabe qual padrão esquecemos! As correspondências em Rust são exaustivas: devemos esgotar todas as possibilidades para que o código seja válido. Especialmente no caso de Option<T>
, quando Rust nos impede de esquecer de lidar explicitamente com o caso None
, ele nos protege de assumir que temos um valor quando poderíamos ter nulo, tornando assim impossível o erro de bilhões de dólares discutido anteriormente.
O espaço reservado _
Rust também tem um padrão que podemos usar quando não queremos listar todos os valores possíveis. Por exemplo, a u8
pode ter valores válidos de 0 a 255. Se nos preocupamos apenas com os valores 1, 3, 5 e 7, não queremos ter que listar 0, 2, 4, 6, 8, 9 todo o caminho até 255. Felizmente, não precisamos: podemos usar o padrão especial _
em vez disso:
O padrão _
corresponderá a qualquer valor. Colocando-o após nossos outros braços, o _
irá corresponder a todos os casos possíveis que não foram especificados antes dele. O ()
é apenas o valor da unidade, portanto, nada acontecerá no caso _
. Como resultado, podemos dizer que não queremos fazer nada para todos os valores possíveis que não listamos antes do espaço reservado _
.
No entanto, a expressão match
pode ser um pouco prolixa em uma situação na qual nos importamos com apenas um dos casos. Para esta situação, a Rust fornece if let
.
Mais sobre padrões e correspondência podem ser encontrados no capítulo 18.
Traduzido por Acervo Lima. O original pode ser acessado aqui.
0 comentários:
Postar um comentário