Enums em Rust
Um enum (abreviação de “enumeração”) define um tipo que pode ser uma entre um conjunto fixo de possibilidades. Se uma struct serve para dizer “esta coisa tem todos estes campos”, um enum serve para dizer “esta coisa é exatamente uma destas opções”.
Criando e usando um enum
Seção intitulada “Criando e usando um enum”enum Direction { North, South, East, West,}
fn main() { let heading = Direction::North;
match heading { Direction::North => println!("Indo para o norte"), Direction::South => println!("Indo para o sul"), Direction::East => println!("Indo para o leste"), Direction::West => println!("Indo para o oeste"), }}Saída:
Indo para o norteRepare que esse match não precisa de um _ para “pegar todo o resto”. Como todas as Direction possíveis estão listadas, o Rust já sabe que o match está completo.
Enums podem guardar dados
Seção intitulada “Enums podem guardar dados”É aqui que os enums do Rust vão além dos enums comuns de outras linguagens: cada variante pode carregar seus próprios dados:
enum Shape { Circle(f64), // raio Rectangle(f64, f64), // largura, altura}
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 3.14159 * radius * radius, Shape::Rectangle(width, height) => width * height, }}
fn main() { let circle = Shape::Circle(3.0); let rectangle = Shape::Rectangle(4.0, 5.0);
println!("{:.2}", area(&circle)); println!("{:.2}", area(&rectangle));}Saída:
28.2720.00Shape::Circle carrega um f64 (o raio); Shape::Rectangle carrega dois. Em cada braço, o match já tira esses dados de dentro da variante para você, sem nenhum passo extra para “desembrulhar”.
Enums também podem ter métodos
Seção intitulada “Enums também podem ter métodos”Assim como uma struct, um enum pode ter um bloco impl com métodos. Dentro de um método, você normalmente faz match self para decidir o que fazer em cada variante:
#[derive(Debug)]enum Light { Red, Yellow, Green,}
impl Light { fn seconds(&self) -> u32 { match self { Light::Red => 30, Light::Yellow => 3, Light::Green => 25, } }
fn next(&self) -> Light { match self { Light::Red => Light::Green, Light::Green => Light::Yellow, Light::Yellow => Light::Red, } }}
fn main() { let mut light = Light::Red;
for _ in 0..4 { println!("{:?} por {} segundos", light, light.seconds()); light = light.next(); }}Saída:
Red por 30 segundosGreen por 25 segundosYellow por 3 segundosRed por 30 segundosnext devolve um Light novinho, e light = light.next(); troca o antigo pelo novo. É por isso que light foi declarado como mut. O #[derive(Debug)] funciona em enums do mesmo jeito que em structs, então {:?} imprime o nome da variante (Red = vermelho, Yellow = amarelo, Green = verde).
Colocar a lógica em métodos deixa tudo perto do tipo. Quem usa um Light pode chamar light.seconds() sem saber como o tempo é decidido. E, se você adicionar uma variante nova depois, como FlashingYellow (amarelo piscante), o compilador aponta cada match que você precisa atualizar.
Option: um enum que você já vem usando
Seção intitulada “Option: um enum que você já vem usando”O tipo Option, que você vai conhecer direito na página dele, é só um enum definido na biblioteca padrão:
enum Option<T> { Some(T), None,}Depois que você entende enums, Option deixa de parecer uma sintaxe especial. É um enum totalmente comum, com duas variantes, e uma delas por acaso carrega um valor.
Por que não usar uma struct com um campo de “tipo”?
Seção intitulada “Por que não usar uma struct com um campo de “tipo”?”Você poderia tentar imitar isso com uma struct e um campo de texto, como "circle" ou "rectangle". Só que aí nada impede você de digitar "circl" por engano, e você teria que verificar na mão quais campos realmente fazem sentido para cada tipo. Com um enum, combinações inválidas nem chegam a ser possíveis de escrever, e o compilador obriga você a tratar todas as variantes.
Prática
Seção intitulada “Prática”1. Conte as moedas
Seção intitulada “1. Conte as moedas”Crie um enum Coin (moeda) com as variantes Penny, Nickel, Dime e Quarter (as moedas americanas de 1, 5, 10 e 25 centavos). Escreva fn value_in_cents(coin: &Coin) -> u32 e depois some uma carteira de moedas: um quarter, um dime, um nickel, um penny e mais um quarter. Dica: guarde as moedas em um array e percorra &purse.
Ver solução
enum Coin { Penny, Nickel, Dime, Quarter,}
fn value_in_cents(coin: &Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, }}
fn main() { let purse = [Coin::Quarter, Coin::Dime, Coin::Nickel, Coin::Penny, Coin::Quarter];
let mut total = 0; for coin in &purse { total += value_in_cents(coin); }
println!("Total: {total} centavos");}Saída:
Total: 66 centavos2. Adicione um triângulo
Seção intitulada “2. Adicione um triângulo”Pegue o enum Shape desta página e adicione uma variante Triangle que guarda o comprimento dos três lados. Depois, escreva uma função perimeter (o perímetro, a distância em volta da forma toda) que funcione para todas as formas. Dica: o perímetro de um círculo é 2.0 * 3.14159 * radius. O de um triângulo é a soma dos três lados.
Ver solução
enum Shape { Circle(f64), Rectangle(f64, f64), Triangle(f64, f64, f64),}
fn perimeter(shape: &Shape) -> f64 { match shape { Shape::Circle(radius) => 2.0 * 3.14159 * radius, Shape::Rectangle(width, height) => 2.0 * (width + height), Shape::Triangle(a, b, c) => a + b + c, }}
fn main() { let shapes = [ Shape::Circle(1.0), Shape::Rectangle(4.0, 5.0), Shape::Triangle(3.0, 4.0, 5.0), ];
for shape in &shapes { println!("{:.2}", perimeter(shape)); }}Saída:
6.2818.0012.00Experimente apagar o braço Shape::Triangle do match. O Rust se recusa a compilar, porque o match não cobre mais todas as variantes. É o compilador garantindo que você nunca esqueça um caso.
3. Temperaturas
Seção intitulada “3. Temperaturas”Crie um enum Temperature com duas variantes que guardam um f64 cada: Celsius e Fahrenheit. Adicione um método to_celsius(&self) -> f64 e imprima Celsius(25.0), Fahrenheit(212.0) e Fahrenheit(50.0) em °C, com uma casa decimal. Dica: para converter, use (f - 32.0) * 5.0 / 9.0.
Ver solução
enum Temperature { Celsius(f64), Fahrenheit(f64),}
impl Temperature { fn to_celsius(&self) -> f64 { match self { Temperature::Celsius(c) => *c, Temperature::Fahrenheit(f) => (f - 32.0) * 5.0 / 9.0, } }}
fn main() { let readings = [ Temperature::Celsius(25.0), Temperature::Fahrenheit(212.0), Temperature::Fahrenheit(50.0), ];
for t in &readings { println!("{:.1} °C", t.to_celsius()); }}Saída:
25.0 °C100.0 °C10.0 °CComo self é emprestado, c e f são referências (&f64). A conta funciona direto com referências, mas, para devolver o valor em Celsius puro, você escreve *c, que quer dizer “o número para o qual c aponta”.
- Um enum define um tipo como uma entre várias variantes com nome:
enum Direction { North, South, ... }. - As variantes podem carregar seus próprios dados, e cada variante pode carregar dados diferentes.
- O
matchdesestrutura os dados do enum de forma natural e exige que todas as variantes sejam tratadas. - Enums podem ter métodos em um bloco
impl, assim como structs. Option(eResult, que você verá mais adiante) são só enums da biblioteca padrão. Não têm nada de mágico.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. O que cada variante de um enum em Rust pode fazer, ao contrário de um enum comum em outras linguagens?
2. Por que o match do exemplo com Direction não precisa de um braço _ para o resto?
3. Como Option<T> é implementado de fato na biblioteca padrão?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- Defining an Enum — The Rust Book (em inglês): variantes com dados, métodos em enums e por que o Rust tem
Optionem vez de null. - The
matchControl Flow Construct — The Rust Book (em inglês):matchem enums a fundo, com um exemplo de separar moedas e padrões “pega-tudo”. - Enums — Rust by Example (em inglês): exemplos executáveis, inclusive variantes com campos nomeados, como
Click { x: i64, y: i64 }. - Enumerations — The Rust Reference (em inglês): as regras completas, como dar valores numéricos explícitos às variantes.
Option— documentação da std (em inglês): a definição de verdade do enum que você mais vai usar, com todos os métodos auxiliares.