Enums
Esta aula aborda enums em Rust, desde sua definição básica até variantes com dados, o enum Option e casos de uso práticos. Você aprenderá como enums permitem representar tipos que podem assumir diferentes formas, com segurança de tipo garantida pelo compilador.
Enums (enumerações) são um recurso poderoso em Rust para definir tipos que podem ser um de vários valores possíveis. Diferente de linguagens como C ou Java, os enums em Rust não são apenas números; cada variante pode carregar dados de tipos diferentes, tornando-os extremamente versáteis. Nesta aula, exploraremos como definir enums, como associar dados a cada variante e como usar o famoso enum Option para lidar com valores opcionais de forma segura.
Definindo enums
Em Rust, um enum é definido com a palavra-chave enum seguida do nome e das variantes entre chaves. Cada variante é separada por vírgula. Por exemplo, podemos representar os estados de um semáforo:
enum TrafficLight {
Red,
Yellow,
Green,
}
Para usar um valor do enum, usamos a sintaxe NomeDoEnum::Variante:
let light = TrafficLight::Red;
Enums podem ser combinados com match para executar código diferente para cada variante, garantindo que todos os casos sejam tratados. O compilador verifica exaustividade, ou seja, se você esqueceu de tratar alguma variante, o código não compila.
Variantes com dados
Cada variante de um enum pode carregar dados de tipos diferentes, como tuplas, structs ou tipos primitivos. Isso permite representar conceitos complexos de forma concisa. Por exemplo, um enum para mensagens de chat:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
A variante Quit não tem dados; Move tem dois campos nomeados; Write tem uma String; ChangeColor tem três valores i32. Para criar uma instância:
let msg1 = Message::Write(String::from("Olá"));
let msg2 = Message::Move { x: 10, y: 20 };
No match, podemos extrair os dados de cada variante usando padrões:
match msg1 {
Message::Quit => println!("Saindo"),
Message::Move { x, y } => println!("Movendo para ({}, {})", x, y),
Message::Write(text) => println!("Texto: {}", text),
Message::ChangeColor(r, g, b) => println!("Cor: RGB({}, {}, {})", r, g, b),
}
Option como enum
Rust não tem null. Em vez disso, usa o enum Option<T> para representar um valor que pode existir ou não. Sua definição na biblioteca padrão é:
enum Option<T> {
None,
Some(T),
}
Isso força o programador a tratar ambos os casos, eliminando erros de ponteiro nulo. Exemplo:
fn dividir(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
let resultado = dividir(10.0, 2.0);
match resultado {
Some(valor) => println!("Resultado: {}", valor),
None => println!("Erro: divisão por zero"),
}
O Option também oferece métodos úteis como unwrap, expect, map, and_then, etc., que permitem trabalhar com valores opcionais de forma funcional e segura.
Casos de uso
Enums são amplamente usados em Rust para modelar domínios com estados bem definidos. Exemplos comuns:
- Máquinas de estado: Representar estados de um jogo (Menu, Jogando, Pausado, GameOver).
- Tratamento de erros: Combinado com
Result<T, E>, que também é um enum (Ok(T)eErr(E)). - Dados heterogêneos: Armazenar diferentes tipos em uma única lista, por exemplo, uma lista de tokens em um interpretador.
- Configuração: Representar opções de configuração que podem ser de tipos diferentes.
Exemplo de enum para representar formas geométricas:
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64, f64),
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle(a, b, c) => {
let s = (a + b + c) / 2.0;
(s * (s - a) * (s - b) * (s - c)).sqrt()
}
}
}
Boas práticas
- Use enums para representar estados mutuamente exclusivos com dados associados.
- Prefira
Optiona valores sentinela como -1 ou null. - Sempre use
matchexaustivo para garantir que todos os casos sejam tratados. - Evite enums com muitas variantes; considere usar traits ou structs separadas se o número for grande.
Referências
- The Rust Programming Language - Defining an Enum
- The Rust Programming Language - The match Control Flow Operator
- The Rust Programming Language - Concise Control Flow with if let
- Rust Standard Library - Option
- Rust by Example - Enums
- The Rust Programming Language - Generics in Enum Definitions
Exercícios
- Defina um enum chamado
Statuscom variantesSuccess(contendo uma String),Error(contendo um código i32 e uma mensagem) ePending. Crie uma função que recebe umStatuse imprime uma mensagem apropriada. - Escreva uma função que recebe um
Option<String>e retorna o comprimento da string se existir, ou 0 se forNone. - Crie um enum
Temperaturecom variantesCelsius(f64)eFahrenheit(f64). Implemente um métodoto_celsiusque converte para Celsius. - Use o enum
Optionpara implementar uma funçãosafe_divideque retornaOption<f64>. Se o denominador for zero, retorneNone. - Defina um enum
WebEventcom variantesPageLoad,KeyPress(char),Click { x: i64, y: i64 }. Crie uma função que recebe um vetor de eventos e imprime uma descrição de cada um.
enum Status {
Success(String),
Error(i32, String),
Pending,
}
fn process_status(status: Status) {
match status {
Status::Success(msg) => println!("Sucesso: {}", msg),
Status::Error(code, msg) => println!("Erro {}: {}", code, msg),
Status::Pending => println!("Aguardando..."),
}
}
fn length_or_zero(s: Option<String>) -> usize {
match s {
Some(text) => text.len(),
None => 0,
}
}
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 safe_divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
enum WebEvent {
PageLoad,
KeyPress(char),
Click { x: i64, y: i64 },
}
fn handle_events(events: Vec<WebEvent>) {
for event in events {
match event {
WebEvent::PageLoad => println!("Página carregada"),
WebEvent::KeyPress(c) => println!("Tecla pressionada: {}", c),
WebEvent::Click { x, y } => println!("Clique em ({}, {})", x, y),
}
}
}