Enums: tipos soma em Rust
1. Introdução aos Enums em Rust
Enums (enumerações) em Rust são muito mais poderosos do que em linguagens tradicionais como C ou Java. Enquanto em C um enum é meramente um conjunto de constantes inteiras nomeadas, em Rust os enums são tipos soma — um conceito da teoria dos tipos que significa que um valor pode ser exatamente uma de várias possibilidades diferentes.
A diferença fundamental é que cada variante de um enum em Rust pode carregar seus próprios dados, de tipos potencialmente distintos. Isso permite modelar domínios complexos de forma segura e expressiva.
// Exemplo básico de enum
enum DiaDaSemana {
Segunda,
Terca,
Quarta,
Quinta,
Sexta,
Sabado,
Domingo,
}
2. Definindo e instanciando Enums
A sintaxe de definição é direta: usamos a palavra-chave enum, seguida pelo nome e pelas variantes dentro de chaves. Cada variante pode ser simples ou conter dados associados.
// Variantes simples
enum Cor {
Vermelho,
Verde,
Azul,
}
// Variantes com dados associados
enum EnderecoIP {
V4(u8, u8, u8, u8), // quatro octetos
V6(String), // endereço como string
}
// Criando valores
let cor_favorita = Cor::Azul;
let localhost = EnderecoIP::V4(127, 0, 0, 1);
let ipv6_teste = EnderecoIP::V6(String::from("::1"));
Enums também podem ter discriminantes inteiros explícitos, úteis para interoperabilidade:
enum StatusHttp {
Ok = 200,
NotFound = 404,
InternalServerError = 500,
}
let codigo = StatusHttp::NotFound as u32; // converte para 404
3. Variantes com Dados: Tipos Produto dentro de Tipos Soma
O poder real dos enums em Rust vem da capacidade de associar dados diferentes a cada variante. Isso combina tipos produto (structs, tuplas) dentro de um tipo soma.
enum Mensagem {
Sair,
Mover(i32, i32), // tupla com dados
Escrever(String), // dado único
MudarCor { r: u8, g: u8, b: u8 }, // struct anônima
}
// Aninhamento com structs nomeadas
struct Ponto {
x: f64,
y: f64,
}
enum Forma {
Circulo { raio: f64 },
Retangulo { largura: f64, altura: f64 },
Ponto(Ponto), // struct como dado associado
}
4. Pattern Matching com match
O match é a ferramenta principal para trabalhar com enums. Ele permite desestruturar cada variante e extrair seus dados de forma segura e exaustiva.
fn processar_mensagem(msg: Mensagem) {
match msg {
Mensagem::Sair => {
println!("Encerrando...");
}
Mensagem::Mover(x, y) => {
println!("Movendo para ({}, {})", x, y);
}
Mensagem::Escrever(texto) => {
println!("Texto: {}", texto);
}
Mensagem::MudarCor { r, g, b } => {
println!("Nova cor: RGB({}, {}, {})", r, g, b);
}
}
}
// O compilador garante exaustividade!
// O pattern _ (coringa) captura qualquer variante não listada
fn dia_eh_util(dia: DiaDaSemana) -> bool {
match dia {
DiaDaSemana::Sabado | DiaDaSemana::Domingo => false,
_ => true, // todas as outras variantes
}
}
5. O Enum Option<T>: Eliminando Null
Rust não tem null. Em vez disso, usa o enum Option<T> para representar a presença ou ausência de um valor:
// Definição padrão (já na biblioteca padrão)
// enum Option<T> {
// None,
// Some(T),
// }
fn dividir(numerador: f64, denominador: f64) -> Option<f64> {
if denominador == 0.0 {
None // divisão por zero
} else {
Some(numerador / denominador)
}
}
let resultado = dividir(10.0, 2.0);
// Formas seguras de extrair o valor
if let Some(valor) = resultado {
println!("Resultado: {}", valor);
}
// Ou usando métodos
let valor = resultado.unwrap_or(0.0); // valor padrão se None
let valor = resultado.expect("Divisão falhou!"); // mensagem de erro se None
// Verificações booleanas
if resultado.is_some() {
println!("Temos um resultado!");
}
6. O Enum Result<T, E>: Tratamento de Erros
Enquanto Option lida com ausência, Result lida com falhas que carregam informações sobre o erro:
// Definição padrão
// enum Result<T, E> {
// Ok(T),
// Err(E),
// }
use std::fs::File;
use std::io::{self, Read};
fn ler_arquivo(caminho: &str) -> Result<String, io::Error> {
let mut arquivo = File::open(caminho)?; // operador ? propaga o erro
let mut conteudo = String::new();
arquivo.read_to_string(&mut conteudo)?;
Ok(conteudo)
}
// Uso com match
match ler_arquivo("dados.txt") {
Ok(conteudo) => println!("Conteúdo: {}", conteudo),
Err(erro) => eprintln!("Erro ao ler arquivo: {}", erro),
}
// O operador ? simplifica a propagação em funções que retornam Result
fn processar() -> Result<(), io::Error> {
let dados = ler_arquivo("config.txt")?; // propaga erro automaticamente
println!("Configuração: {}", dados);
Ok(())
}
7. Métodos e Blocos impl para Enums
Enums podem ter métodos implementados, assim como structs:
enum Mensagem {
Sair,
Mover(i32, i32),
Escrever(String),
}
impl Mensagem {
fn executar(&self) {
match self {
Mensagem::Sair => println!("Tchau!"),
Mensagem::Mover(x, y) => println!("Movendo para ({}, {})", x, y),
Mensagem::Escrever(texto) => println!("{}", texto),
}
}
fn descricao(&self) -> String {
match self {
Mensagem::Sair => String::from("Comando de saída"),
Mensagem::Mover(_, _) => String::from("Comando de movimento"),
Mensagem::Escrever(_) => String::from("Comando de escrita"),
}
}
}
let msg = Mensagem::Mover(10, 20);
msg.executar();
println!("Tipo: {}", msg.descricao());
8. Enums Recursivos e o Uso de Box
Enums recursivos (que contêm a si mesmos) precisam de alocação no heap porque o tamanho do enum seria infinito em tempo de compilação. Usamos Box para isso:
// Lista encadeada usando enum recursivo
enum Lista {
Cons(i32, Box<Lista>), // Box aloca no heap
Nil, // fim da lista
}
use Lista::{Cons, Nil};
fn main() {
let lista = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
// Percorrendo a lista
let mut atual = &lista;
loop {
match atual {
Cons(valor, proximo) => {
print!("{} ", valor);
atual = proximo;
}
Nil => {
println!();
break;
}
}
}
}
O Box permite que o compilador saiba o tamanho do enum (sempre o tamanho de um ponteiro para o heap), resolvendo o problema de tamanho indeterminado.
Referências
- The Rust Programming Language - Enums and Pattern Matching — Capítulo oficial do livro sobre enums, com exemplos detalhados de
Option,matcheif let - Rust by Example - Enums — Exemplos práticos de definição e uso de enums, incluindo variantes com dados
- Rust Reference - Enum Types — Documentação de referência oficial sobre a sintaxe e semântica de enums
- The Rustonomicon - Exhaustiveness and Pattern Matching — Explicação avançada sobre verificação de exaustividade em
match - Rust RFC 2195 - Enum Variants with Fields — RFC que introduziu suporte a variantes de enum com campos nomeados (struct-like)
- Learning Rust With Entirely Too Many Linked Lists — Tutorial aprofundado sobre enums recursivos e gerenciamento de memória com
Box